ims-mcp 1.0.4__py3-none-any.whl → 1.0.6__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/__init__.py CHANGED
@@ -11,7 +11,7 @@ Environment Variables:
11
11
  Note: Environment variables use R2R_ prefix for compatibility with underlying R2R SDK.
12
12
  """
13
13
 
14
- __version__ = "1.0.4"
14
+ __version__ = "1.0.6"
15
15
  __author__ = "Igor Solomatov"
16
16
 
17
17
  from ims_mcp.server import mcp
ims_mcp/server.py CHANGED
@@ -14,10 +14,11 @@ The R2RClient automatically reads these environment variables, so no manual
14
14
  configuration is needed when running via uvx or other launchers.
15
15
  """
16
16
 
17
+ import functools
17
18
  import os
18
19
  import sys
19
20
  import uuid
20
- from r2r import R2RClient
21
+ from r2r import R2RClient, R2RException
21
22
 
22
23
  # Global client instance with authentication
23
24
  _authenticated_client = None
@@ -76,6 +77,34 @@ def get_authenticated_client() -> R2RClient:
76
77
  return client
77
78
 
78
79
 
80
+ def retry_on_auth_error(func):
81
+ """Decorator to handle token expiry and automatically re-authenticate.
82
+
83
+ When R2R server restarts, all authentication tokens are invalidated.
84
+ This decorator catches 401/403 errors, invalidates the cached client,
85
+ re-authenticates, and retries the request once.
86
+ """
87
+ def invalidate_client():
88
+ """Invalidate cached client to force re-authentication."""
89
+ global _authenticated_client
90
+ _authenticated_client = None
91
+
92
+ @functools.wraps(func)
93
+ async def wrapper(*args, **kwargs):
94
+ try:
95
+ return await func(*args, **kwargs)
96
+ except R2RException as e:
97
+ # Check if this is an authentication error (token expired)
98
+ if hasattr(e, 'status_code') and e.status_code in [401, 403]:
99
+ print(f"[ims-mcp] Token expired, re-authenticating...", file=sys.stderr)
100
+ invalidate_client()
101
+ # Retry once with fresh authentication
102
+ return await func(*args, **kwargs)
103
+ # Re-raise non-auth errors
104
+ raise
105
+ return wrapper
106
+
107
+
79
108
  def id_to_shorthand(id: str) -> str:
80
109
  """Convert a full ID to shortened version for display."""
81
110
  return str(id)[:7]
@@ -167,6 +196,7 @@ except Exception as e:
167
196
 
168
197
  # Search tool with filtering support
169
198
  @mcp.tool()
199
+ @retry_on_auth_error
170
200
  async def search(
171
201
  query: str,
172
202
  filters: dict | None = None,
@@ -216,6 +246,7 @@ async def search(
216
246
 
217
247
  # RAG query tool with filtering and generation config
218
248
  @mcp.tool()
249
+ @retry_on_auth_error
219
250
  async def rag(
220
251
  query: str,
221
252
  filters: dict | None = None,
@@ -270,6 +301,7 @@ async def rag(
270
301
 
271
302
  # Document upload tool with upsert semantics
272
303
  @mcp.tool()
304
+ @retry_on_auth_error
273
305
  async def put_document(
274
306
  content: str,
275
307
  title: str,
@@ -337,6 +369,7 @@ async def put_document(
337
369
 
338
370
  # List documents tool
339
371
  @mcp.tool()
372
+ @retry_on_auth_error
340
373
  async def list_documents(
341
374
  offset: float = 0, # Use float to accept JSON "number" type, convert to int internally
342
375
  limit: float = 100, # Use float to accept JSON "number" type, convert to int internally
@@ -440,6 +473,7 @@ async def list_documents(
440
473
 
441
474
  # Get document tool
442
475
  @mcp.tool()
476
+ @retry_on_auth_error
443
477
  async def get_document(
444
478
  document_id: str | None = None,
445
479
  title: str | None = None,
@@ -504,6 +538,12 @@ async def get_document(
504
538
 
505
539
  # Found exactly one match
506
540
  document_id = matching_docs[0][0]
541
+ except R2RException as e:
542
+ # Re-raise authentication errors so the decorator can handle them
543
+ if hasattr(e, 'status_code') and e.status_code in [401, 403]:
544
+ raise
545
+ # For other R2RExceptions, return error message
546
+ return f"Error searching for document by title: {str(e)}"
507
547
  except Exception as e:
508
548
  return f"Error searching for document by title: {str(e)}"
509
549
 
@@ -521,6 +561,15 @@ async def get_document(
521
561
 
522
562
  return "\n".join(output_lines)
523
563
 
564
+ except R2RException as e:
565
+ # Re-raise authentication errors so the decorator can handle them
566
+ if hasattr(e, 'status_code') and e.status_code in [401, 403]:
567
+ raise
568
+ # For other R2RExceptions, return error message
569
+ error_msg = str(e)
570
+ if "not found" in error_msg.lower():
571
+ return f"Error: Document with ID '{document_id}' not found"
572
+ return f"Error downloading document: {error_msg}"
524
573
  except Exception as e:
525
574
  error_msg = str(e)
526
575
  if "not found" in error_msg.lower():
@@ -530,6 +579,7 @@ async def get_document(
530
579
 
531
580
  # Delete document tool
532
581
  @mcp.tool()
582
+ @retry_on_auth_error
533
583
  async def delete_document(document_id: str) -> str:
534
584
  """
535
585
  Delete a document by ID
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ims-mcp
3
- Version: 1.0.4
3
+ Version: 1.0.6
4
4
  Summary: Model Context Protocol server for IMS (Instruction Management Systems)
5
5
  Author: Igor Solomatov
6
6
  License-Expression: MIT
@@ -0,0 +1,9 @@
1
+ ims_mcp/__init__.py,sha256=zAgLyO8bIgLsWcPtGOcLMLa08lhMgsyOysJWkm_tXE8,631
2
+ ims_mcp/__main__.py,sha256=z4P1aCVfOgS3cTM2wgJd2pxjMmKCkGkiqYDRGgrspxw,191
3
+ ims_mcp/server.py,sha256=_yNZrBhHKFoJiSLwZqACxqvLvJuaFdZ1jntGCHQIzlU,23787
4
+ ims_mcp-1.0.6.dist-info/licenses/LICENSE,sha256=4d1dlH04mbnN3ya4lybcVOUwljRHGy-aSc9MYqGYW44,2534
5
+ ims_mcp-1.0.6.dist-info/METADATA,sha256=nQWzol6KOBdwge8j9fVLYEJfHhmfJ59_1m5tBdG4T_8,9295
6
+ ims_mcp-1.0.6.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
7
+ ims_mcp-1.0.6.dist-info/entry_points.txt,sha256=xCH9I8g1pTTEqrfjnE-ANHaZo4W6EBJVy0Lg5z8SaIQ,48
8
+ ims_mcp-1.0.6.dist-info/top_level.txt,sha256=wEXA33qFr_eov3S1PY2OF6EQBA2rtAWB_ZNJOzNNQuM,8
9
+ ims_mcp-1.0.6.dist-info/RECORD,,
@@ -1,9 +0,0 @@
1
- ims_mcp/__init__.py,sha256=6hX8dryQog_XJDdunq29fTc8Pih9ApWYgabwaAE3DGI,631
2
- ims_mcp/__main__.py,sha256=z4P1aCVfOgS3cTM2wgJd2pxjMmKCkGkiqYDRGgrspxw,191
3
- ims_mcp/server.py,sha256=g7ceKx7Bxjr7LGBGfbn3AFHK2rjcPv30Y_Rhfuy3GZQ,21734
4
- ims_mcp-1.0.4.dist-info/licenses/LICENSE,sha256=4d1dlH04mbnN3ya4lybcVOUwljRHGy-aSc9MYqGYW44,2534
5
- ims_mcp-1.0.4.dist-info/METADATA,sha256=_GIf5S7xRw-FmX7U8psxa5MlYFPINanV2tTflGmP4hY,9295
6
- ims_mcp-1.0.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
7
- ims_mcp-1.0.4.dist-info/entry_points.txt,sha256=xCH9I8g1pTTEqrfjnE-ANHaZo4W6EBJVy0Lg5z8SaIQ,48
8
- ims_mcp-1.0.4.dist-info/top_level.txt,sha256=wEXA33qFr_eov3S1PY2OF6EQBA2rtAWB_ZNJOzNNQuM,8
9
- ims_mcp-1.0.4.dist-info/RECORD,,