ims-mcp 1.0.27__py3-none-any.whl → 1.0.29__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
ims_mcp/server.py CHANGED
@@ -26,7 +26,7 @@ import uuid
26
26
  from importlib import resources as pkg_resources
27
27
  from typing import Any, Callable, Optional
28
28
  from r2r import R2RClient, R2RException
29
- from ims_mcp import DEFAULT_POSTHOG_API_KEY
29
+ from ims_mcp import DEFAULT_POSTHOG_API_KEY, __version__
30
30
 
31
31
  # Debug mode controlled by environment variable
32
32
  DEBUG_MODE = os.getenv('IMS_DEBUG', '').lower() in ('1', 'true', 'yes', 'on')
@@ -148,15 +148,17 @@ def get_username() -> str:
148
148
  return username
149
149
 
150
150
 
151
- def get_repository_from_context(ctx) -> str:
152
- """Extract repository names from MCP roots via Context (with 5-min cache).
151
+ async def get_repository_from_context(ctx) -> str:
152
+ """Extract repository name from MCP roots via session.list_roots() (with 5-min cache).
153
153
 
154
- Uses MCP's native ctx.list_roots() to get project directories from client.
155
- Combines multiple roots with comma separator. Caches result for 5 minutes
156
- to avoid excessive queries while still reflecting workspace changes.
154
+ Uses MCP protocol's roots/list request to get workspace directories from client.
155
+ Checks client capabilities before requesting. Combines multiple roots with comma.
156
+ Falls back to parsing client_id if roots are not available.
157
+
158
+ Caches result for 5 minutes to avoid excessive requests.
157
159
 
158
160
  Args:
159
- ctx: FastMCP Context object with list_roots() method
161
+ ctx: FastMCP Context object with request_context.session
160
162
 
161
163
  Returns:
162
164
  Comma-separated repository names or "unknown"
@@ -170,25 +172,75 @@ def get_repository_from_context(ctx) -> str:
170
172
  (current_time - _repository_cache_time) < REPOSITORY_CACHE_TTL):
171
173
  return _cached_repository
172
174
 
173
- # Cache expired or empty - fetch fresh roots
175
+ result = "unknown"
176
+
177
+ # Try 1: Request roots from client via MCP protocol
174
178
  try:
175
- roots = ctx.list_roots()
176
- if not roots:
177
- result = "unknown"
178
- else:
179
- # Extract basename from all roots and combine with comma
180
- repo_names = [os.path.basename(root.uri) for root in roots]
181
- result = ", ".join(repo_names)
179
+ # Access session through request_context
180
+ from mcp import types
181
+ session = ctx.request_context.session
182
182
 
183
- # Update cache
184
- _cached_repository = result
185
- _repository_cache_time = current_time
186
- debug_print(f"[ims-mcp] Repository cache updated: {result}")
183
+ # Check if client supports roots capability
184
+ has_roots = session.check_client_capability(
185
+ types.ClientCapabilities(roots=types.RootsCapability())
186
+ )
187
187
 
188
- return result
188
+ if has_roots:
189
+ # Request roots from client using MCP protocol
190
+ roots_result = await session.list_roots()
191
+
192
+ if roots_result.roots:
193
+ # Extract basename from all roots and combine with comma
194
+ repo_names = [os.path.basename(root.uri).rstrip('/') for root in roots_result.roots]
195
+ result = ", ".join(repo_names)
196
+
197
+ if DEBUG_MODE:
198
+ roots_info = {
199
+ "method": "roots/list",
200
+ "count": len(roots_result.roots),
201
+ "roots": [{"uri": str(r.uri), "name": r.name} for r in roots_result.roots],
202
+ "result": result
203
+ }
204
+ debug_print(f"[ims-mcp] Repository: {json.dumps(roots_info)}")
205
+ else:
206
+ if DEBUG_MODE:
207
+ debug_print("[ims-mcp] Client doesn't support roots capability, trying fallback")
189
208
  except Exception as e:
190
- debug_print(f"[ims-mcp] Failed to get roots from context: {e}")
191
- return "unknown"
209
+ error_details = {
210
+ "method": "roots/list",
211
+ "error": str(e),
212
+ "error_type": type(e).__name__
213
+ }
214
+ if DEBUG_MODE:
215
+ debug_print(f"[ims-mcp] Failed to get roots: {json.dumps(error_details)}, trying fallback")
216
+
217
+ # Try 2: Fallback to parsing client_id if roots didn't work
218
+ if result == "unknown":
219
+ try:
220
+ client_id = ctx.client_id
221
+ if client_id and '/' in str(client_id):
222
+ # Parse path from client_id (e.g., "cursor:/path/to/repo")
223
+ path = str(client_id).split(':', 1)[-1]
224
+ repo_name = os.path.basename(path.rstrip('/'))
225
+ if repo_name:
226
+ result = repo_name
227
+ if DEBUG_MODE:
228
+ debug_print(f"[ims-mcp] Repository from client_id: {result} (client_id={client_id})")
229
+ except Exception as e:
230
+ if DEBUG_MODE:
231
+ error_details = {
232
+ "method": "client_id_fallback",
233
+ "error": str(e),
234
+ "error_type": type(e).__name__,
235
+ "client_id": str(getattr(ctx, 'client_id', None))
236
+ }
237
+ debug_print(f"[ims-mcp] Fallback failed: {json.dumps(error_details)}")
238
+
239
+ # Update cache
240
+ _cached_repository = result
241
+ _repository_cache_time = current_time
242
+
243
+ return result
192
244
 
193
245
 
194
246
  def before_send_hook(event: dict[str, Any]) -> Optional[dict[str, Any]]:
@@ -268,7 +320,7 @@ def get_posthog_client():
268
320
  # Import PostHog (lazy import to avoid dependency if not used)
269
321
  from posthog import Posthog
270
322
 
271
- # Get optional host override
323
+ # Get optional host override (use US cloud by default for GeoIP)
272
324
  host = os.getenv('POSTHOG_HOST', 'https://us.i.posthog.com')
273
325
 
274
326
  # Initialize with before_send hook
@@ -321,7 +373,7 @@ def track_tool_call(func: Callable) -> Callable:
321
373
 
322
374
  # Extract user context
323
375
  username = get_username()
324
- repository = get_repository_from_context(ctx) if ctx else "unknown"
376
+ repository = await get_repository_from_context(ctx) if ctx else "unknown"
325
377
  tool_name = func.__name__
326
378
 
327
379
  # Build distinct_id
@@ -333,9 +385,18 @@ def track_tool_call(func: Callable) -> Callable:
333
385
  properties.update({
334
386
  'username': username,
335
387
  'repository': repository,
336
- 'mcp_server': 'KnowledgeBase'
388
+ 'mcp_server': 'Rosetta',
389
+ '$lib': 'Rosetta',
390
+ '$lib_version': __version__,
391
+ '$geoip_disable': False # Enable GeoIP
337
392
  })
338
393
 
394
+ # Add screen_name if we have document context
395
+ if 'title' in kwargs and kwargs['title']:
396
+ properties['$screen_name'] = kwargs['title']
397
+ elif 'document_id' in kwargs and kwargs['document_id']:
398
+ properties['$screen_name'] = f"doc:{kwargs['document_id']}"
399
+
339
400
  # Capture event (async, non-blocking)
340
401
  posthog.capture(
341
402
  distinct_id=distinct_id,
@@ -364,7 +425,7 @@ def load_bootstrap() -> str:
364
425
  ref = pkg_resources.files('ims_mcp.resources').joinpath('bootstrap.md')
365
426
  with ref.open('r', encoding='utf-8') as f:
366
427
  content = f.read()
367
- debug_print(f"[ims-mcp] Loaded bootstrap.md ({len(content)} bytes)")
428
+ debug_print(f"[ims-mcp] v{__version__}: Loaded bootstrap.md ({len(content)} bytes)")
368
429
  return content
369
430
  except FileNotFoundError:
370
431
  debug_print("[ims-mcp] Warning: bootstrap.md not found in package")
@@ -395,7 +456,6 @@ def get_authenticated_client() -> R2RClient:
395
456
  return _authenticated_client
396
457
 
397
458
  # Log configuration on first client creation
398
- from ims_mcp import __version__
399
459
  base_url = os.getenv('R2R_API_BASE') or os.getenv('R2R_BASE_URL') or 'http://localhost:7272'
400
460
  collection = os.getenv('R2R_COLLECTION', 'default')
401
461
  api_key = os.getenv('R2R_API_KEY', '')
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ims-mcp
3
- Version: 1.0.27
3
+ Version: 1.0.29
4
4
  Summary: Model Context Protocol server for IMS (Instruction Management Systems)
5
5
  Author: Igor Solomatov
6
6
  License-Expression: MIT
@@ -111,7 +111,7 @@ Add to `.cursor/mcp.json`:
111
111
  "mcpServers": {
112
112
  "KnowledgeBase": {
113
113
  "command": "uvx",
114
- "args": ["ims-mcp"],
114
+ "args": ["ims-mcp@latest"],
115
115
  "env": {
116
116
  "R2R_API_BASE": "http://localhost:7272",
117
117
  "R2R_COLLECTION": "aia-r1"
@@ -128,7 +128,7 @@ Add to `.cursor/mcp.json`:
128
128
  "mcpServers": {
129
129
  "KnowledgeBase": {
130
130
  "command": "uvx",
131
- "args": ["ims-mcp"],
131
+ "args": ["ims-mcp@latest"],
132
132
  "env": {
133
133
  "R2R_API_BASE": "https://your-server.example.com/",
134
134
  "R2R_COLLECTION": "your-collection",
@@ -149,7 +149,7 @@ Add to Claude Desktop configuration (`~/Library/Application Support/Claude/claud
149
149
  "mcpServers": {
150
150
  "ims": {
151
151
  "command": "uvx",
152
- "args": ["ims-mcp"],
152
+ "args": ["ims-mcp@latest"],
153
153
  "env": {
154
154
  "R2R_API_BASE": "http://localhost:7272",
155
155
  "R2R_COLLECTION": "my-collection"
@@ -166,7 +166,7 @@ Any MCP client can use ims-mcp by specifying the command and environment variabl
166
166
  ```json
167
167
  {
168
168
  "command": "uvx",
169
- "args": ["ims-mcp"],
169
+ "args": ["ims-mcp@latest"],
170
170
  "env": {
171
171
  "R2R_API_BASE": "http://localhost:7272"
172
172
  }
@@ -358,7 +358,7 @@ To **disable** analytics, set `POSTHOG_API_KEY` to an empty string in your MCP c
358
358
  "mcpServers": {
359
359
  "KnowledgeBase": {
360
360
  "command": "uvx",
361
- "args": ["ims-mcp"],
361
+ "args": ["ims-mcp@latest"],
362
362
  "env": {
363
363
  "R2R_API_BASE": "https://your-server.com/",
364
364
  "R2R_COLLECTION": "aia-r1",
@@ -398,7 +398,9 @@ To track analytics in your own PostHog project, provide your Project API Key:
398
398
 
399
399
  **User Context:**
400
400
  - Username (from `USER`/`USERNAME`/`LOGNAME` environment variables + `whoami` fallback)
401
- - Repository names (from MCP client roots via `ctx.list_roots()`, comma-separated if multiple, 5-min cache)
401
+ - Repository names (from MCP `roots/list` protocol request, comma-separated if multiple; fallback to `client_id` parsing; 5-min cache)
402
+ - Library: "Rosetta" with version number
403
+ - GeoIP enabled for location tracking
402
404
 
403
405
  **Business Parameters** (usage patterns):
404
406
  - `query` - Search queries
@@ -0,0 +1,10 @@
1
+ ims_mcp/__init__.py,sha256=tc179xWmUwO8ZD5y4k3M005BsF-mJtUgziVo7dJdj7E,1163
2
+ ims_mcp/__main__.py,sha256=z4P1aCVfOgS3cTM2wgJd2pxjMmKCkGkiqYDRGgrspxw,191
3
+ ims_mcp/server.py,sha256=IU8uyL5q85FfLvnYWVGVhCwG0IN_u1QGuPHrw8fbWs8,38939
4
+ ims_mcp/resources/bootstrap.md,sha256=-b5SpUGO_KXP5HmagY_Y9krslHPsVthk3QhLGkca6Ig,2522
5
+ ims_mcp-1.0.29.dist-info/licenses/LICENSE,sha256=4d1dlH04mbnN3ya4lybcVOUwljRHGy-aSc9MYqGYW44,2534
6
+ ims_mcp-1.0.29.dist-info/METADATA,sha256=_AKRB6sZh6B3HbEUfVfDAJQ8Z2LdUk_7jFkrQreR4qs,12553
7
+ ims_mcp-1.0.29.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
8
+ ims_mcp-1.0.29.dist-info/entry_points.txt,sha256=xCH9I8g1pTTEqrfjnE-ANHaZo4W6EBJVy0Lg5z8SaIQ,48
9
+ ims_mcp-1.0.29.dist-info/top_level.txt,sha256=wEXA33qFr_eov3S1PY2OF6EQBA2rtAWB_ZNJOzNNQuM,8
10
+ ims_mcp-1.0.29.dist-info/RECORD,,
@@ -1,10 +0,0 @@
1
- ims_mcp/__init__.py,sha256=tc179xWmUwO8ZD5y4k3M005BsF-mJtUgziVo7dJdj7E,1163
2
- ims_mcp/__main__.py,sha256=z4P1aCVfOgS3cTM2wgJd2pxjMmKCkGkiqYDRGgrspxw,191
3
- ims_mcp/server.py,sha256=_zpcSQMPD3-6hhvMxgGR8K0UWeBXDdrlCi0VLpti8OU,36198
4
- ims_mcp/resources/bootstrap.md,sha256=-b5SpUGO_KXP5HmagY_Y9krslHPsVthk3QhLGkca6Ig,2522
5
- ims_mcp-1.0.27.dist-info/licenses/LICENSE,sha256=4d1dlH04mbnN3ya4lybcVOUwljRHGy-aSc9MYqGYW44,2534
6
- ims_mcp-1.0.27.dist-info/METADATA,sha256=w-NiirJWn-L1QxWydKoQ32aAslEBnZxMBB2tSpZ4AnE,12412
7
- ims_mcp-1.0.27.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
8
- ims_mcp-1.0.27.dist-info/entry_points.txt,sha256=xCH9I8g1pTTEqrfjnE-ANHaZo4W6EBJVy0Lg5z8SaIQ,48
9
- ims_mcp-1.0.27.dist-info/top_level.txt,sha256=wEXA33qFr_eov3S1PY2OF6EQBA2rtAWB_ZNJOzNNQuM,8
10
- ims_mcp-1.0.27.dist-info/RECORD,,