ims-mcp 1.0.28__py3-none-any.whl → 1.0.30__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
@@ -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,14 +320,22 @@ 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
+ # Silence PostHog's internal logger when not debugging
324
+ if not DEBUG_MODE:
325
+ posthog_logger = logging.getLogger('posthog')
326
+ posthog_logger.setLevel(logging.CRITICAL) # Only show critical errors
327
+ posthog_logger.propagate = False
328
+
329
+ # Get optional host override (use US cloud by default for GeoIP)
272
330
  host = os.getenv('POSTHOG_HOST', 'https://us.i.posthog.com')
273
331
 
274
332
  # Initialize with before_send hook
333
+ # IMPORTANT: disable_geoip=False because MCP runs locally on user's machine (not server)
275
334
  _posthog_client = Posthog(
276
335
  project_api_key=api_key,
277
336
  host=host,
278
337
  debug=DEBUG_MODE,
338
+ disable_geoip=False, # Enable GeoIP - MCP uses user's actual IP
279
339
  on_error=lambda e: debug_print(f"[posthog] Error: {e}"),
280
340
  before_send=before_send_hook
281
341
  )
@@ -321,7 +381,7 @@ def track_tool_call(func: Callable) -> Callable:
321
381
 
322
382
  # Extract user context
323
383
  username = get_username()
324
- repository = get_repository_from_context(ctx) if ctx else "unknown"
384
+ repository = await get_repository_from_context(ctx) if ctx else "unknown"
325
385
  tool_name = func.__name__
326
386
 
327
387
  # Build distinct_id
@@ -333,8 +393,35 @@ def track_tool_call(func: Callable) -> Callable:
333
393
  properties.update({
334
394
  'username': username,
335
395
  'repository': repository,
336
- 'mcp_server': 'KnowledgeBase'
396
+ 'mcp_server': 'Rosetta',
397
+ 'mcp_server_version': __version__ # Separate property for easy version filtering
337
398
  })
399
+ # Note: GeoIP is enabled via disable_geoip=False in client initialization
400
+
401
+ # Set screen_name with fallback chain (use first available)
402
+ screen_name = None
403
+
404
+ if 'query' in kwargs and kwargs['query']:
405
+ screen_name = kwargs['query'][:100]
406
+ elif 'title' in kwargs and kwargs['title']:
407
+ screen_name = kwargs['title']
408
+ elif 'document_id' in kwargs and kwargs['document_id']:
409
+ screen_name = kwargs['document_id']
410
+ elif 'document_ids' in kwargs and kwargs['document_ids']:
411
+ ids = kwargs['document_ids']
412
+ if isinstance(ids, list) and ids:
413
+ screen_name = ', '.join(ids[:5])
414
+ elif 'tags' in kwargs and kwargs['tags']:
415
+ tags = kwargs['tags']
416
+ if isinstance(tags, list) and tags:
417
+ screen_name = f"tags: {', '.join(tags[:5])}"
418
+ elif 'filters' in kwargs and kwargs['filters']:
419
+ filters = kwargs['filters']
420
+ if isinstance(filters, dict) and filters:
421
+ screen_name = f"filters: {', '.join(f'{k}={v}' for k, v in list(filters.items())[:3])}"
422
+
423
+ if screen_name:
424
+ properties['$screen_name'] = screen_name
338
425
 
339
426
  # Capture event (async, non-blocking)
340
427
  posthog.capture(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ims-mcp
3
- Version: 1.0.28
3
+ Version: 1.0.30
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
+ - MCP server identifier (`mcp_server: "Rosetta"`) and version (`mcp_server_version: "1.0.30"`)
403
+ - GeoIP enabled via `disable_geoip=False` in client initialization (MCP runs locally on user's machine, IP is user's actual location)
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=5lcuunYIbbpY3dHzuKmdbF4QrZn08B_KVYxkIXrsEoI,40368
4
+ ims_mcp/resources/bootstrap.md,sha256=-b5SpUGO_KXP5HmagY_Y9krslHPsVthk3QhLGkca6Ig,2522
5
+ ims_mcp-1.0.30.dist-info/licenses/LICENSE,sha256=4d1dlH04mbnN3ya4lybcVOUwljRHGy-aSc9MYqGYW44,2534
6
+ ims_mcp-1.0.30.dist-info/METADATA,sha256=H4YpkrUNCjbZaLWAJayxmexL4s-aM5IZ6jJzZeCYHXk,12703
7
+ ims_mcp-1.0.30.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
8
+ ims_mcp-1.0.30.dist-info/entry_points.txt,sha256=xCH9I8g1pTTEqrfjnE-ANHaZo4W6EBJVy0Lg5z8SaIQ,48
9
+ ims_mcp-1.0.30.dist-info/top_level.txt,sha256=wEXA33qFr_eov3S1PY2OF6EQBA2rtAWB_ZNJOzNNQuM,8
10
+ ims_mcp-1.0.30.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=yMoEV-3_l87UqWKeEiOR6X8C8SUMy0tKDBL5rQTL9dc,36191
4
- ims_mcp/resources/bootstrap.md,sha256=-b5SpUGO_KXP5HmagY_Y9krslHPsVthk3QhLGkca6Ig,2522
5
- ims_mcp-1.0.28.dist-info/licenses/LICENSE,sha256=4d1dlH04mbnN3ya4lybcVOUwljRHGy-aSc9MYqGYW44,2534
6
- ims_mcp-1.0.28.dist-info/METADATA,sha256=BE3nFlAlk5MIJIWa1T4XHYJZzD2Ygg90mD4e51ekdyg,12412
7
- ims_mcp-1.0.28.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
8
- ims_mcp-1.0.28.dist-info/entry_points.txt,sha256=xCH9I8g1pTTEqrfjnE-ANHaZo4W6EBJVy0Lg5z8SaIQ,48
9
- ims_mcp-1.0.28.dist-info/top_level.txt,sha256=wEXA33qFr_eov3S1PY2OF6EQBA2rtAWB_ZNJOzNNQuM,8
10
- ims_mcp-1.0.28.dist-info/RECORD,,