|
| 1 | +# MCP Session Architecture and Project Root Detection |
| 2 | + |
| 3 | +## Overview |
| 4 | + |
| 5 | +This document explains how Model Context Protocol (MCP) sessions work, how project root detection is implemented, and how MCP servers can access session information from clients like Cursor, Claude Desktop, and other MCP-compatible tools. |
| 6 | + |
| 7 | +## How MCP Sessions Are Created |
| 8 | + |
| 9 | +### Client-Side Session Creation |
| 10 | + |
| 11 | +**Important**: The MCP session is **created by the MCP client** (Cursor, Claude Desktop, etc.), not by the MCP server. The server receives this session object and can extract project information from it. |
| 12 | + |
| 13 | +When an MCP client starts a server, it creates a session object containing: |
| 14 | + |
| 15 | +- **`session.roots`** - Workspace/project directories the client wants to expose |
| 16 | +- **`session.env`** - Environment variables from the client context |
| 17 | +- **`session.capabilities`** - Features the client supports |
| 18 | +- **`session.loggingLevel`** - Logging level set by the client |
| 19 | + |
| 20 | +### How `session.roots` Gets Populated |
| 21 | + |
| 22 | +The `session.roots` array is populated differently by each MCP client: |
| 23 | + |
| 24 | +#### **Cursor IDE:** |
| 25 | +- Automatically passes the **currently open workspace root** as `session.roots[0].uri` |
| 26 | +- Uses the workspace directory you have open in Cursor |
| 27 | +- Happens automatically when Cursor starts the MCP server process |
| 28 | + |
| 29 | +#### **Claude Desktop:** |
| 30 | +- Uses the working directory where the MCP server process is started |
| 31 | +- Can be configured in Claude Desktop's MCP configuration |
| 32 | + |
| 33 | +#### **Other MCP Clients:** |
| 34 | +- Implementation varies by client |
| 35 | +- Usually current working directory or configured project paths |
| 36 | + |
| 37 | +## Session Object Structure |
| 38 | + |
| 39 | +```javascript |
| 40 | +{ |
| 41 | + roots: [ |
| 42 | + { |
| 43 | + uri: "file:///Users/username/workspace/project", // Project root URI |
| 44 | + name: "project" // Optional name |
| 45 | + } |
| 46 | + // Can contain multiple roots |
| 47 | + ], |
| 48 | + env: { |
| 49 | + // Environment variables from the client |
| 50 | + TASK_MASTER_PROJECT_ROOT: "/custom/path", // If set by user |
| 51 | + DEBUG: "true", |
| 52 | + // ... other environment variables |
| 53 | + }, |
| 54 | + capabilities: { |
| 55 | + // Features the client supports |
| 56 | + progress: true, // Can show progress indicators |
| 57 | + sampling: false, // Can request LLM completions |
| 58 | + // ... other capabilities |
| 59 | + }, |
| 60 | + loggingLevel: "info" // Logging level preference |
| 61 | +} |
| 62 | +``` |
| 63 | + |
| 64 | +## URI Format and Normalization |
| 65 | + |
| 66 | +The `session.roots[0].uri` contains a **file:// URI** that requires processing: |
| 67 | + |
| 68 | +1. **URI Decoding** - Handle spaces and special characters |
| 69 | +2. **Protocol Stripping** - Remove `file://` prefix |
| 70 | +3. **Path Normalization** - Handle OS-specific paths (especially Windows `/C:/...`) |
| 71 | + |
| 72 | +### Example URI Processing |
| 73 | + |
| 74 | +```javascript |
| 75 | +// Input from session |
| 76 | +const rawUri = "file:///Users/username/My%20Project"; |
| 77 | + |
| 78 | +// Processing steps |
| 79 | +const decoded = decodeURIComponent(rawUri); // "file:///Users/username/My Project" |
| 80 | +const withoutProtocol = decoded.replace('file://', ''); // "/Users/username/My Project" |
| 81 | + |
| 82 | +// Handle Windows paths (remove leading slash from /C:/...) |
| 83 | +const normalized = withoutProtocol.startsWith('/') && /[A-Za-z]:/.test(withoutProtocol.substring(1, 3)) |
| 84 | + ? withoutProtocol.substring(1) // Remove leading slash for Windows |
| 85 | + : withoutProtocol; |
| 86 | + |
| 87 | +const finalPath = path.resolve(normalized); // OS-normalized absolute path |
| 88 | +``` |
| 89 | + |
| 90 | +## Accessing the Session Object in MCP Tools |
| 91 | + |
| 92 | +### Basic Pattern |
| 93 | + |
| 94 | +```javascript |
| 95 | +server.addTool({ |
| 96 | + name: 'example_tool', |
| 97 | + description: 'Shows how to access session data', |
| 98 | + parameters: z.object({ |
| 99 | + // tool parameters |
| 100 | + }), |
| 101 | + execute: async (args, { log, session }) => { |
| 102 | + // Access session properties |
| 103 | + const projectRoot = session?.roots?.[0]?.uri; |
| 104 | + const envVars = session?.env; |
| 105 | + const capabilities = session?.capabilities; |
| 106 | + |
| 107 | + log.info(`Project root: ${projectRoot}`); |
| 108 | + log.info(`Environment vars: ${JSON.stringify(envVars)}`); |
| 109 | + |
| 110 | + return "Tool executed with session data"; |
| 111 | + } |
| 112 | +}); |
| 113 | +``` |
| 114 | +
|
| 115 | +### Real-World Example (Task Master) |
| 116 | +
|
| 117 | +```javascript |
| 118 | +// From Task Master's get-tasks tool |
| 119 | +execute: withNormalizedProjectRoot(async (args, { log, session }) => { |
| 120 | + try { |
| 121 | + // Session is used to resolve project paths |
| 122 | + let tasksJsonPath = resolveTasksPath(args, session); |
| 123 | + let complexityReportPath = resolveComplexityReportPath(args, session); |
| 124 | + |
| 125 | + // Tool logic using resolved paths... |
| 126 | + const result = await listTasksDirect({ |
| 127 | + tasksJsonPath: tasksJsonPath, |
| 128 | + // ... other parameters |
| 129 | + }, log); |
| 130 | + |
| 131 | + return handleApiResult(result, log, 'Error getting tasks'); |
| 132 | + } catch (error) { |
| 133 | + log.error(`Error: ${error.message}`); |
| 134 | + return createErrorResponse(error.message); |
| 135 | + } |
| 136 | +}) |
| 137 | +``` |
| 138 | +
|
| 139 | +### Context Object Structure |
| 140 | +
|
| 141 | +The complete context object passed to MCP tools: |
| 142 | +
|
| 143 | +```javascript |
| 144 | +{ |
| 145 | + log: { // Logger with methods |
| 146 | + info(msg), // Log info message |
| 147 | + warn(msg), // Log warning |
| 148 | + error(msg), // Log error |
| 149 | + debug(msg) // Log debug info |
| 150 | + }, |
| 151 | + session: { // MCP session from client |
| 152 | + roots: [...], // Project roots |
| 153 | + env: {...}, // Environment variables |
| 154 | + capabilities: {...}, // Client capabilities |
| 155 | + loggingLevel: "info" |
| 156 | + } |
| 157 | + // ... other MCP context properties |
| 158 | +} |
| 159 | +``` |
| 160 | +
|
| 161 | +## Project Root Detection Strategy |
| 162 | +
|
| 163 | +A robust MCP server should implement a hierarchical fallback system: |
| 164 | +
|
| 165 | +### 1. Environment Variable Override (Highest Priority) |
| 166 | +```javascript |
| 167 | +if (process.env.PROJECT_ROOT) { |
| 168 | + return process.env.PROJECT_ROOT; |
| 169 | +} |
| 170 | +if (session?.env?.PROJECT_ROOT) { |
| 171 | + return session.env.PROJECT_ROOT; |
| 172 | +} |
| 173 | +``` |
| 174 | +
|
| 175 | +### 2. Explicit Parameter |
| 176 | +```javascript |
| 177 | +if (args.projectRoot) { |
| 178 | + return normalizeProjectRoot(args.projectRoot); |
| 179 | +} |
| 180 | +``` |
| 181 | +
|
| 182 | +### 3. Session-Based Detection |
| 183 | +```javascript |
| 184 | +if (session?.roots?.[0]?.uri) { |
| 185 | + return normalizeUri(session.roots[0].uri); |
| 186 | +} |
| 187 | +``` |
| 188 | +
|
| 189 | +### 4. Project Marker Search |
| 190 | +```javascript |
| 191 | +// Search upward for project indicators |
| 192 | +const markers = ['.git', '.taskmaster', 'package.json', 'go.mod']; |
| 193 | +return findProjectRoot(process.cwd(), markers); |
| 194 | +``` |
| 195 | +
|
| 196 | +### 5. Current Directory (Fallback) |
| 197 | +```javascript |
| 198 | +return process.cwd(); |
| 199 | +``` |
| 200 | +
|
| 201 | +## Implementation Considerations |
| 202 | +
|
| 203 | +### For MCP Server Developers |
| 204 | +
|
| 205 | +1. **Always access session through context parameter** |
| 206 | +2. **Implement proper URI normalization** for cross-platform compatibility |
| 207 | +3. **Use hierarchical fallback** for project root detection |
| 208 | +4. **Handle missing session gracefully** (some clients may not provide full session data) |
| 209 | +5. **Log session usage** for debugging client integration issues |
| 210 | +
|
| 211 | +### For MCP Client Developers |
| 212 | +
|
| 213 | +1. **Always populate `session.roots`** with workspace directories |
| 214 | +2. **Use proper file:// URI format** for root paths |
| 215 | +3. **Include environment variables** in `session.env` |
| 216 | +4. **Set appropriate capabilities** in `session.capabilities` |
| 217 | +5. **Maintain session consistency** across tool calls |
| 218 | +
|
| 219 | +## Testing Session Handling |
| 220 | +
|
| 221 | +### Unit Test Example |
| 222 | +
|
| 223 | +```javascript |
| 224 | +// Test session-based project root detection |
| 225 | +const mockSession = { |
| 226 | + roots: [ |
| 227 | + { uri: "file:///Users/test/workspace/project" } |
| 228 | + ], |
| 229 | + env: {}, |
| 230 | + capabilities: {} |
| 231 | +}; |
| 232 | + |
| 233 | +const result = await toolFunction(args, { log: mockLog, session: mockSession }); |
| 234 | +// Assert result uses correct project root |
| 235 | +``` |
| 236 | +
|
| 237 | +### Integration Test |
| 238 | +
|
| 239 | +```javascript |
| 240 | +// Test with actual MCP client |
| 241 | +const client = new MCPClient("path/to/server"); |
| 242 | +await client.connect(); |
| 243 | + |
| 244 | +// Verify server receives session data correctly |
| 245 | +const tools = await client.listTools(); |
| 246 | +const result = await client.callTool("get_project_info", {}); |
| 247 | +``` |
| 248 | +
|
| 249 | +## Security Considerations |
| 250 | +
|
| 251 | +1. **Validate session data** - Don't trust session content blindly |
| 252 | +2. **Sanitize file paths** - Prevent directory traversal attacks |
| 253 | +3. **Respect client boundaries** - Only access paths in `session.roots` |
| 254 | +4. **Environment variable safety** - Be cautious with `session.env` data |
| 255 | +
|
| 256 | +## Compatibility Notes |
| 257 | +
|
| 258 | +- **FastMCP (Python)**: Full session support with automatic normalization |
| 259 | +- **FastMCP (TypeScript)**: Complete session handling implementation |
| 260 | +- **Official MCP SDKs**: Basic session support, may need manual normalization |
| 261 | +- **Custom implementations**: Session support varies |
| 262 | +
|
| 263 | +## Conclusion |
| 264 | +
|
| 265 | +The MCP session architecture provides a standardized way for clients to communicate workspace context to servers. By properly implementing session handling, MCP servers can automatically detect project roots and provide seamless integration with development tools like Cursor. |
| 266 | +
|
| 267 | +The key insight is that **clients create and populate the session** - servers should extract and normalize this information rather than trying to detect project context independently. |
0 commit comments