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 +87 -27
- {ims_mcp-1.0.27.dist-info → ims_mcp-1.0.29.dist-info}/METADATA +9 -7
- ims_mcp-1.0.29.dist-info/RECORD +10 -0
- ims_mcp-1.0.27.dist-info/RECORD +0 -10
- {ims_mcp-1.0.27.dist-info → ims_mcp-1.0.29.dist-info}/WHEEL +0 -0
- {ims_mcp-1.0.27.dist-info → ims_mcp-1.0.29.dist-info}/entry_points.txt +0 -0
- {ims_mcp-1.0.27.dist-info → ims_mcp-1.0.29.dist-info}/licenses/LICENSE +0 -0
- {ims_mcp-1.0.27.dist-info → ims_mcp-1.0.29.dist-info}/top_level.txt +0 -0
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
|
|
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
|
|
155
|
-
Combines multiple roots with comma
|
|
156
|
-
to
|
|
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
|
|
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
|
-
|
|
175
|
+
result = "unknown"
|
|
176
|
+
|
|
177
|
+
# Try 1: Request roots from client via MCP protocol
|
|
174
178
|
try:
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
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
|
-
#
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
183
|
+
# Check if client supports roots capability
|
|
184
|
+
has_roots = session.check_client_capability(
|
|
185
|
+
types.ClientCapabilities(roots=types.RootsCapability())
|
|
186
|
+
)
|
|
187
187
|
|
|
188
|
-
|
|
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
|
-
|
|
191
|
-
|
|
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': '
|
|
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.
|
|
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
|
|
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,,
|
ims_mcp-1.0.27.dist-info/RECORD
DELETED
|
@@ -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,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|