nia-mcp-server 1.0.21__tar.gz → 1.0.23__tar.gz
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.
Potentially problematic release.
This version of nia-mcp-server might be problematic. Click here for more details.
- {nia_mcp_server-1.0.21 → nia_mcp_server-1.0.23}/PKG-INFO +1 -1
- {nia_mcp_server-1.0.21 → nia_mcp_server-1.0.23}/pyproject.toml +1 -1
- {nia_mcp_server-1.0.21 → nia_mcp_server-1.0.23}/src/nia_mcp_server/__init__.py +2 -1
- {nia_mcp_server-1.0.21 → nia_mcp_server-1.0.23}/src/nia_mcp_server/api_client.py +193 -4
- {nia_mcp_server-1.0.21 → nia_mcp_server-1.0.23}/src/nia_mcp_server/server.py +678 -1
- {nia_mcp_server-1.0.21 → nia_mcp_server-1.0.23}/.gitignore +0 -0
- {nia_mcp_server-1.0.21 → nia_mcp_server-1.0.23}/ARCHITECTURE.md +0 -0
- {nia_mcp_server-1.0.21 → nia_mcp_server-1.0.23}/LICENSE +0 -0
- {nia_mcp_server-1.0.21 → nia_mcp_server-1.0.23}/README.md +0 -0
- {nia_mcp_server-1.0.21 → nia_mcp_server-1.0.23}/nia_analytics.log +0 -0
- {nia_mcp_server-1.0.21 → nia_mcp_server-1.0.23}/nia_mcp_server.log +0 -0
- {nia_mcp_server-1.0.21 → nia_mcp_server-1.0.23}/src/nia_mcp_server/__main__.py +0 -0
- {nia_mcp_server-1.0.21 → nia_mcp_server-1.0.23}/src/nia_mcp_server/assets/rules/claude_rules.md +0 -0
- {nia_mcp_server-1.0.21 → nia_mcp_server-1.0.23}/src/nia_mcp_server/assets/rules/cursor_rules.md +0 -0
- {nia_mcp_server-1.0.21 → nia_mcp_server-1.0.23}/src/nia_mcp_server/assets/rules/nia_rules.md +0 -0
- {nia_mcp_server-1.0.21 → nia_mcp_server-1.0.23}/src/nia_mcp_server/assets/rules/vscode_rules.md +0 -0
- {nia_mcp_server-1.0.21 → nia_mcp_server-1.0.23}/src/nia_mcp_server/assets/rules/windsurf_rules.md +0 -0
- {nia_mcp_server-1.0.21 → nia_mcp_server-1.0.23}/src/nia_mcp_server/cli.py +0 -0
- {nia_mcp_server-1.0.21 → nia_mcp_server-1.0.23}/src/nia_mcp_server/profiles.py +0 -0
- {nia_mcp_server-1.0.21 → nia_mcp_server-1.0.23}/src/nia_mcp_server/project_init.py +0 -0
- {nia_mcp_server-1.0.21 → nia_mcp_server-1.0.23}/src/nia_mcp_server/rule_transformer.py +0 -0
- {nia_mcp_server-1.0.21 → nia_mcp_server-1.0.23}/src/nia_mcp_server/setup.py +0 -0
|
@@ -28,7 +28,7 @@ class NIAApiClient:
|
|
|
28
28
|
self.client = httpx.AsyncClient(
|
|
29
29
|
headers={
|
|
30
30
|
"Authorization": f"Bearer {api_key}",
|
|
31
|
-
"User-Agent": "nia-mcp-server/1.0.
|
|
31
|
+
"User-Agent": "nia-mcp-server/1.0.23",
|
|
32
32
|
"Content-Type": "application/json"
|
|
33
33
|
},
|
|
34
34
|
timeout=720.0 # 12 minute timeout for deep research operations
|
|
@@ -685,18 +685,44 @@ class NIAApiClient:
|
|
|
685
685
|
"source_identifier": source_identifier,
|
|
686
686
|
"metadata": metadata or {}
|
|
687
687
|
}
|
|
688
|
-
|
|
688
|
+
|
|
689
689
|
response = await self.client.post(
|
|
690
690
|
f"{self.base_url}/v2/sources/content",
|
|
691
691
|
json=payload
|
|
692
692
|
)
|
|
693
693
|
response.raise_for_status()
|
|
694
694
|
return response.json()
|
|
695
|
-
|
|
695
|
+
|
|
696
696
|
except httpx.HTTPStatusError as e:
|
|
697
697
|
raise self._handle_api_error(e)
|
|
698
698
|
except Exception as e:
|
|
699
699
|
raise APIError(f"Failed to get source content: {str(e)}")
|
|
700
|
+
|
|
701
|
+
async def submit_bug_report(
|
|
702
|
+
self,
|
|
703
|
+
description: str,
|
|
704
|
+
bug_type: str = "bug",
|
|
705
|
+
additional_context: Optional[str] = None
|
|
706
|
+
) -> Dict[str, Any]:
|
|
707
|
+
"""Submit a bug report or feature request."""
|
|
708
|
+
try:
|
|
709
|
+
payload = {
|
|
710
|
+
"description": description,
|
|
711
|
+
"bug_type": bug_type,
|
|
712
|
+
"additional_context": additional_context
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
response = await self.client.post(
|
|
716
|
+
f"{self.base_url}/v2/bug-report",
|
|
717
|
+
json=payload
|
|
718
|
+
)
|
|
719
|
+
response.raise_for_status()
|
|
720
|
+
return response.json()
|
|
721
|
+
|
|
722
|
+
except httpx.HTTPStatusError as e:
|
|
723
|
+
raise self._handle_api_error(e)
|
|
724
|
+
except Exception as e:
|
|
725
|
+
raise APIError(f"Failed to submit bug report: {str(e)}")
|
|
700
726
|
|
|
701
727
|
async def index_local_filesystem(
|
|
702
728
|
self,
|
|
@@ -887,4 +913,167 @@ class NIAApiClient:
|
|
|
887
913
|
except httpx.HTTPStatusError as e:
|
|
888
914
|
raise self._handle_api_error(e)
|
|
889
915
|
except Exception as e:
|
|
890
|
-
raise APIError(f"Failed to read package file: {str(e)}")
|
|
916
|
+
raise APIError(f"Failed to read package file: {str(e)}")
|
|
917
|
+
|
|
918
|
+
# ========================================================================
|
|
919
|
+
# CONTEXT SHARING METHODS
|
|
920
|
+
# ========================================================================
|
|
921
|
+
|
|
922
|
+
async def save_context(
|
|
923
|
+
self,
|
|
924
|
+
title: str,
|
|
925
|
+
summary: str,
|
|
926
|
+
content: str,
|
|
927
|
+
agent_source: str,
|
|
928
|
+
tags: List[str] = None,
|
|
929
|
+
metadata: Dict[str, Any] = None
|
|
930
|
+
) -> Dict[str, Any]:
|
|
931
|
+
"""Save a conversation context for cross-agent sharing."""
|
|
932
|
+
try:
|
|
933
|
+
payload = {
|
|
934
|
+
"title": title,
|
|
935
|
+
"summary": summary,
|
|
936
|
+
"content": content,
|
|
937
|
+
"agent_source": agent_source,
|
|
938
|
+
"tags": tags or [],
|
|
939
|
+
"metadata": metadata or {}
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
response = await self.client.post(
|
|
943
|
+
f"{self.base_url}/v2/contexts",
|
|
944
|
+
json=payload
|
|
945
|
+
)
|
|
946
|
+
response.raise_for_status()
|
|
947
|
+
return response.json()
|
|
948
|
+
|
|
949
|
+
except httpx.HTTPStatusError as e:
|
|
950
|
+
raise self._handle_api_error(e)
|
|
951
|
+
except Exception as e:
|
|
952
|
+
raise APIError(f"Failed to save context: {str(e)}")
|
|
953
|
+
|
|
954
|
+
async def list_contexts(
|
|
955
|
+
self,
|
|
956
|
+
limit: int = 20,
|
|
957
|
+
offset: int = 0,
|
|
958
|
+
tags: Optional[str] = None,
|
|
959
|
+
agent_source: Optional[str] = None
|
|
960
|
+
) -> Dict[str, Any]:
|
|
961
|
+
"""List user's conversation contexts with pagination and filtering."""
|
|
962
|
+
try:
|
|
963
|
+
params = {
|
|
964
|
+
"limit": limit,
|
|
965
|
+
"offset": offset
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
if tags:
|
|
969
|
+
params["tags"] = tags
|
|
970
|
+
if agent_source:
|
|
971
|
+
params["agent_source"] = agent_source
|
|
972
|
+
|
|
973
|
+
response = await self.client.get(
|
|
974
|
+
f"{self.base_url}/v2/contexts",
|
|
975
|
+
params=params
|
|
976
|
+
)
|
|
977
|
+
response.raise_for_status()
|
|
978
|
+
return response.json()
|
|
979
|
+
|
|
980
|
+
except httpx.HTTPStatusError as e:
|
|
981
|
+
raise self._handle_api_error(e)
|
|
982
|
+
except Exception as e:
|
|
983
|
+
raise APIError(f"Failed to list contexts: {str(e)}")
|
|
984
|
+
|
|
985
|
+
async def get_context(self, context_id: str) -> Dict[str, Any]:
|
|
986
|
+
"""Get a specific conversation context by ID."""
|
|
987
|
+
try:
|
|
988
|
+
response = await self.client.get(f"{self.base_url}/v2/contexts/{context_id}")
|
|
989
|
+
response.raise_for_status()
|
|
990
|
+
return response.json()
|
|
991
|
+
|
|
992
|
+
except httpx.HTTPStatusError as e:
|
|
993
|
+
if e.response.status_code == 404:
|
|
994
|
+
return None
|
|
995
|
+
raise self._handle_api_error(e)
|
|
996
|
+
except Exception as e:
|
|
997
|
+
raise APIError(f"Failed to get context: {str(e)}")
|
|
998
|
+
|
|
999
|
+
async def update_context(
|
|
1000
|
+
self,
|
|
1001
|
+
context_id: str,
|
|
1002
|
+
title: Optional[str] = None,
|
|
1003
|
+
summary: Optional[str] = None,
|
|
1004
|
+
content: Optional[str] = None,
|
|
1005
|
+
tags: Optional[List[str]] = None,
|
|
1006
|
+
metadata: Optional[Dict[str, Any]] = None
|
|
1007
|
+
) -> Dict[str, Any]:
|
|
1008
|
+
"""Update an existing conversation context."""
|
|
1009
|
+
try:
|
|
1010
|
+
payload = {}
|
|
1011
|
+
|
|
1012
|
+
if title is not None:
|
|
1013
|
+
payload["title"] = title
|
|
1014
|
+
if summary is not None:
|
|
1015
|
+
payload["summary"] = summary
|
|
1016
|
+
if content is not None:
|
|
1017
|
+
payload["content"] = content
|
|
1018
|
+
if tags is not None:
|
|
1019
|
+
payload["tags"] = tags
|
|
1020
|
+
if metadata is not None:
|
|
1021
|
+
payload["metadata"] = metadata
|
|
1022
|
+
|
|
1023
|
+
response = await self.client.put(
|
|
1024
|
+
f"{self.base_url}/v2/contexts/{context_id}",
|
|
1025
|
+
json=payload
|
|
1026
|
+
)
|
|
1027
|
+
response.raise_for_status()
|
|
1028
|
+
return response.json()
|
|
1029
|
+
|
|
1030
|
+
except httpx.HTTPStatusError as e:
|
|
1031
|
+
raise self._handle_api_error(e)
|
|
1032
|
+
except Exception as e:
|
|
1033
|
+
raise APIError(f"Failed to update context: {str(e)}")
|
|
1034
|
+
|
|
1035
|
+
async def delete_context(self, context_id: str) -> bool:
|
|
1036
|
+
"""Delete a conversation context."""
|
|
1037
|
+
try:
|
|
1038
|
+
response = await self.client.delete(f"{self.base_url}/v2/contexts/{context_id}")
|
|
1039
|
+
response.raise_for_status()
|
|
1040
|
+
return True
|
|
1041
|
+
|
|
1042
|
+
except httpx.HTTPStatusError as e:
|
|
1043
|
+
if e.response.status_code == 404:
|
|
1044
|
+
return False
|
|
1045
|
+
raise self._handle_api_error(e)
|
|
1046
|
+
except Exception as e:
|
|
1047
|
+
logger.error(f"Failed to delete context: {e}")
|
|
1048
|
+
return False
|
|
1049
|
+
|
|
1050
|
+
async def search_contexts(
|
|
1051
|
+
self,
|
|
1052
|
+
query: str,
|
|
1053
|
+
limit: int = 20,
|
|
1054
|
+
tags: Optional[str] = None,
|
|
1055
|
+
agent_source: Optional[str] = None
|
|
1056
|
+
) -> Dict[str, Any]:
|
|
1057
|
+
"""Search conversation contexts by content, title, or summary."""
|
|
1058
|
+
try:
|
|
1059
|
+
params = {
|
|
1060
|
+
"q": query,
|
|
1061
|
+
"limit": limit
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
if tags:
|
|
1065
|
+
params["tags"] = tags
|
|
1066
|
+
if agent_source:
|
|
1067
|
+
params["agent_source"] = agent_source
|
|
1068
|
+
|
|
1069
|
+
response = await self.client.get(
|
|
1070
|
+
f"{self.base_url}/v2/contexts/search",
|
|
1071
|
+
params=params
|
|
1072
|
+
)
|
|
1073
|
+
response.raise_for_status()
|
|
1074
|
+
return response.json()
|
|
1075
|
+
|
|
1076
|
+
except httpx.HTTPStatusError as e:
|
|
1077
|
+
raise self._handle_api_error(e)
|
|
1078
|
+
except Exception as e:
|
|
1079
|
+
raise APIError(f"Failed to search contexts: {str(e)}")
|
|
@@ -2826,7 +2826,7 @@ async def nia_package_search_read_file(
|
|
|
2826
2826
|
f"- The line range is valid (1-based, max 200 lines)"
|
|
2827
2827
|
)]
|
|
2828
2828
|
|
|
2829
|
-
@mcp.tool()
|
|
2829
|
+
# @mcp.tool()
|
|
2830
2830
|
async def visualize_codebase(
|
|
2831
2831
|
repository: str
|
|
2832
2832
|
) -> List[TextContent]:
|
|
@@ -2979,6 +2979,683 @@ async def visualize_codebase(
|
|
|
2979
2979
|
text=f"❌ Error opening visualization: {str(e)}"
|
|
2980
2980
|
)]
|
|
2981
2981
|
|
|
2982
|
+
|
|
2983
|
+
@mcp.tool()
|
|
2984
|
+
async def nia_bug_report(
|
|
2985
|
+
description: str,
|
|
2986
|
+
bug_type: str = "bug",
|
|
2987
|
+
additional_context: Optional[str] = None
|
|
2988
|
+
) -> List[TextContent]:
|
|
2989
|
+
"""
|
|
2990
|
+
Submit a bug report or feature request to the Nia development team.
|
|
2991
|
+
|
|
2992
|
+
This tool allows users to report bugs, request features, or provide feedback
|
|
2993
|
+
directly to the development team. Reports are sent via email and Slack for
|
|
2994
|
+
immediate attention.
|
|
2995
|
+
|
|
2996
|
+
Args:
|
|
2997
|
+
description: Detailed description of the bug or feature request (10-5000 characters)
|
|
2998
|
+
bug_type: Type of report - "bug", "feature-request", "improvement", or "other" (default: "bug")
|
|
2999
|
+
additional_context: Optional additional context, steps to reproduce, or related information
|
|
3000
|
+
|
|
3001
|
+
Returns:
|
|
3002
|
+
Confirmation of successful submission with reference ID
|
|
3003
|
+
|
|
3004
|
+
Examples:
|
|
3005
|
+
- nia_bug_report("The search is not returning any results for my repository")
|
|
3006
|
+
- nia_bug_report("Add support for searching within specific file types", "feature-request")
|
|
3007
|
+
- nia_bug_report("Repository indexing fails with large repos", "bug", "Happens with repos over 1GB")
|
|
3008
|
+
"""
|
|
3009
|
+
try:
|
|
3010
|
+
client = await ensure_api_client()
|
|
3011
|
+
|
|
3012
|
+
# Validate input parameters
|
|
3013
|
+
if not description or len(description.strip()) < 10:
|
|
3014
|
+
return [
|
|
3015
|
+
TextContent(
|
|
3016
|
+
type="text",
|
|
3017
|
+
text="❌ Error: Bug description must be at least 10 characters long."
|
|
3018
|
+
)
|
|
3019
|
+
]
|
|
3020
|
+
|
|
3021
|
+
if len(description) > 5000:
|
|
3022
|
+
return [
|
|
3023
|
+
TextContent(
|
|
3024
|
+
type="text",
|
|
3025
|
+
text="❌ Error: Bug description must be 5000 characters or less."
|
|
3026
|
+
)
|
|
3027
|
+
]
|
|
3028
|
+
|
|
3029
|
+
valid_types = ["bug", "feature-request", "improvement", "other"]
|
|
3030
|
+
if bug_type not in valid_types:
|
|
3031
|
+
return [
|
|
3032
|
+
TextContent(
|
|
3033
|
+
type="text",
|
|
3034
|
+
text=f"❌ Error: bug_type must be one of: {', '.join(valid_types)}"
|
|
3035
|
+
)
|
|
3036
|
+
]
|
|
3037
|
+
|
|
3038
|
+
if additional_context and len(additional_context) > 2000:
|
|
3039
|
+
return [
|
|
3040
|
+
TextContent(
|
|
3041
|
+
type="text",
|
|
3042
|
+
text="❌ Error: Additional context must be 2000 characters or less."
|
|
3043
|
+
)
|
|
3044
|
+
]
|
|
3045
|
+
|
|
3046
|
+
logger.info(f"Submitting bug report: type={bug_type}, description_length={len(description)}")
|
|
3047
|
+
|
|
3048
|
+
# Submit bug report via API client
|
|
3049
|
+
result = await client.submit_bug_report(
|
|
3050
|
+
description=description.strip(),
|
|
3051
|
+
bug_type=bug_type,
|
|
3052
|
+
additional_context=additional_context.strip() if additional_context else None
|
|
3053
|
+
)
|
|
3054
|
+
|
|
3055
|
+
if result.get("success"):
|
|
3056
|
+
return [
|
|
3057
|
+
TextContent(
|
|
3058
|
+
type="text",
|
|
3059
|
+
text=f"✅ Bug report submitted successfully!\n\n"
|
|
3060
|
+
f"Thank you for your feedback. Your report has been sent to the development team "
|
|
3061
|
+
f"and will be reviewed promptly.\n\n"
|
|
3062
|
+
f"Reference ID: {result.get('message', '').split(': ')[-1] if ': ' in result.get('message', '') else 'N/A'}\n"
|
|
3063
|
+
f"Type: {bug_type.title()}\n"
|
|
3064
|
+
f"Status: The team will be notified immediately via email and Slack.\n\n"
|
|
3065
|
+
f"You can also track issues and feature requests on our GitHub repository:\n"
|
|
3066
|
+
f"https://github.com/nozomio-labs/nia/issues"
|
|
3067
|
+
)
|
|
3068
|
+
]
|
|
3069
|
+
else:
|
|
3070
|
+
return [
|
|
3071
|
+
TextContent(
|
|
3072
|
+
type="text",
|
|
3073
|
+
text=f"❌ Failed to submit bug report: {result.get('message', 'Unknown error')}\n\n"
|
|
3074
|
+
f"Please try again or contact support directly at support@trynia.ai"
|
|
3075
|
+
)
|
|
3076
|
+
]
|
|
3077
|
+
|
|
3078
|
+
except Exception as e:
|
|
3079
|
+
logger.error(f"Error submitting bug report: {e}")
|
|
3080
|
+
return [
|
|
3081
|
+
TextContent(
|
|
3082
|
+
type="text",
|
|
3083
|
+
text=f"❌ Error submitting bug report: {str(e)}\n\n"
|
|
3084
|
+
f"Please try again or contact support directly at support@trynia.ai"
|
|
3085
|
+
)
|
|
3086
|
+
]
|
|
3087
|
+
|
|
3088
|
+
# Context Sharing Tools
|
|
3089
|
+
|
|
3090
|
+
@mcp.tool()
|
|
3091
|
+
async def save_context(
|
|
3092
|
+
title: str,
|
|
3093
|
+
summary: str,
|
|
3094
|
+
content: str,
|
|
3095
|
+
agent_source: str,
|
|
3096
|
+
tags: Optional[List[str]] = None,
|
|
3097
|
+
metadata: Optional[dict] = None
|
|
3098
|
+
) -> List[TextContent]:
|
|
3099
|
+
"""
|
|
3100
|
+
Save a conversation context for cross-agent sharing.
|
|
3101
|
+
|
|
3102
|
+
This tool enables agents to save conversation contexts that can be shared
|
|
3103
|
+
with other AI agents, creating seamless handoffs between different coding
|
|
3104
|
+
environments (e.g., Cursor → Claude Code).
|
|
3105
|
+
|
|
3106
|
+
Args:
|
|
3107
|
+
title: A descriptive title for the context (1-200 characters)
|
|
3108
|
+
summary: Brief summary of the conversation (10-1000 characters)
|
|
3109
|
+
content: Full conversation context - the agent should compact the conversation history but keep all important parts togethers, as well as code snippets. No excuses.
|
|
3110
|
+
agent_source: Which agent is creating this context (e.g., "cursor", "claude-code", "windsurf")
|
|
3111
|
+
tags: Optional list of searchable tags (up to 10 tags)
|
|
3112
|
+
metadata: Optional metadata like file paths, repositories discussed, etc.
|
|
3113
|
+
|
|
3114
|
+
Returns:
|
|
3115
|
+
Confirmation of successful context save with context ID
|
|
3116
|
+
|
|
3117
|
+
Example:
|
|
3118
|
+
save_context(
|
|
3119
|
+
title="Streaming AI SDK Implementation",
|
|
3120
|
+
summary="Planning conversation about implementing streaming responses with AI SDK",
|
|
3121
|
+
content="User asked about implementing streaming... [agent should include conversation]",
|
|
3122
|
+
agent_source="cursor",
|
|
3123
|
+
tags=["streaming", "ai-sdk", "implementation"],
|
|
3124
|
+
metadata={"files": ["src/api/chat.ts"], "repos": ["myproject"]}
|
|
3125
|
+
)
|
|
3126
|
+
"""
|
|
3127
|
+
try:
|
|
3128
|
+
# Validate input parameters
|
|
3129
|
+
if not title or not title.strip():
|
|
3130
|
+
return [TextContent(type="text", text="❌ Error: Title is required")]
|
|
3131
|
+
|
|
3132
|
+
if len(title) > 200:
|
|
3133
|
+
return [TextContent(type="text", text="❌ Error: Title must be 200 characters or less")]
|
|
3134
|
+
|
|
3135
|
+
if not summary or len(summary) < 10:
|
|
3136
|
+
return [TextContent(type="text", text="❌ Error: Summary must be at least 10 characters")]
|
|
3137
|
+
|
|
3138
|
+
if len(summary) > 1000:
|
|
3139
|
+
return [TextContent(type="text", text="❌ Error: Summary must be 1000 characters or less")]
|
|
3140
|
+
|
|
3141
|
+
if not content or len(content) < 50:
|
|
3142
|
+
return [TextContent(type="text", text="❌ Error: Content must be at least 50 characters")]
|
|
3143
|
+
|
|
3144
|
+
if not agent_source or not agent_source.strip():
|
|
3145
|
+
return [TextContent(type="text", text="❌ Error: Agent source is required")]
|
|
3146
|
+
|
|
3147
|
+
if tags and len(tags) > 10:
|
|
3148
|
+
return [TextContent(type="text", text="❌ Error: Maximum 10 tags allowed")]
|
|
3149
|
+
|
|
3150
|
+
client = await ensure_api_client()
|
|
3151
|
+
|
|
3152
|
+
logger.info(f"Saving context: title='{title}', agent={agent_source}, content_length={len(content)}")
|
|
3153
|
+
|
|
3154
|
+
result = await client.save_context(
|
|
3155
|
+
title=title.strip(),
|
|
3156
|
+
summary=summary.strip(),
|
|
3157
|
+
content=content,
|
|
3158
|
+
agent_source=agent_source.strip(),
|
|
3159
|
+
tags=tags or [],
|
|
3160
|
+
metadata=metadata or {}
|
|
3161
|
+
)
|
|
3162
|
+
|
|
3163
|
+
context_id = result.get("id")
|
|
3164
|
+
|
|
3165
|
+
return [TextContent(
|
|
3166
|
+
type="text",
|
|
3167
|
+
text=f"✅ **Context Saved Successfully!**\n\n"
|
|
3168
|
+
f"🆔 **Context ID:** `{context_id}`\n"
|
|
3169
|
+
f"📝 **Title:** {title}\n"
|
|
3170
|
+
f"🤖 **Source Agent:** {agent_source}\n"
|
|
3171
|
+
f"📊 **Content Length:** {len(content):,} characters\n"
|
|
3172
|
+
f"🏷️ **Tags:** {', '.join(tags) if tags else 'None'}\n\n"
|
|
3173
|
+
f"**Next Steps:**\n"
|
|
3174
|
+
f"• Other agents can now retrieve this context using the context ID\n"
|
|
3175
|
+
f"• Use `search_contexts` to find contexts by content or tags\n"
|
|
3176
|
+
f"• Use `list_contexts` to see all your saved contexts\n\n"
|
|
3177
|
+
f"🔗 **Share this context:** Provide the context ID `{context_id}` to other agents"
|
|
3178
|
+
)]
|
|
3179
|
+
|
|
3180
|
+
except APIError as e:
|
|
3181
|
+
logger.error(f"API Error saving context: {e}")
|
|
3182
|
+
return [TextContent(type="text", text=f"❌ API Error: {str(e)}")]
|
|
3183
|
+
except Exception as e:
|
|
3184
|
+
logger.error(f"Error saving context: {e}")
|
|
3185
|
+
return [TextContent(type="text", text=f"❌ Error saving context: {str(e)}")]
|
|
3186
|
+
|
|
3187
|
+
@mcp.tool()
|
|
3188
|
+
async def list_contexts(
|
|
3189
|
+
limit: int = 20,
|
|
3190
|
+
offset: int = 0,
|
|
3191
|
+
tags: Optional[str] = None,
|
|
3192
|
+
agent_source: Optional[str] = None
|
|
3193
|
+
) -> List[TextContent]:
|
|
3194
|
+
"""
|
|
3195
|
+
List saved conversation contexts with pagination and filtering.
|
|
3196
|
+
|
|
3197
|
+
Args:
|
|
3198
|
+
limit: Number of contexts to return (1-100, default: 20)
|
|
3199
|
+
offset: Number of contexts to skip for pagination (default: 0)
|
|
3200
|
+
tags: Comma-separated tags to filter by (optional)
|
|
3201
|
+
agent_source: Filter by specific agent source (optional)
|
|
3202
|
+
|
|
3203
|
+
Returns:
|
|
3204
|
+
List of conversation contexts with pagination info
|
|
3205
|
+
|
|
3206
|
+
Examples:
|
|
3207
|
+
- list_contexts() - List recent 20 contexts
|
|
3208
|
+
- list_contexts(limit=50) - List recent 50 contexts
|
|
3209
|
+
- list_contexts(tags="streaming,ai-sdk") - Filter by tags
|
|
3210
|
+
- list_contexts(agent_source="cursor") - Only contexts from Cursor
|
|
3211
|
+
"""
|
|
3212
|
+
try:
|
|
3213
|
+
# Validate parameters
|
|
3214
|
+
if limit < 1 or limit > 100:
|
|
3215
|
+
return [TextContent(type="text", text="❌ Error: Limit must be between 1 and 100")]
|
|
3216
|
+
|
|
3217
|
+
if offset < 0:
|
|
3218
|
+
return [TextContent(type="text", text="❌ Error: Offset must be 0 or greater")]
|
|
3219
|
+
|
|
3220
|
+
client = await ensure_api_client()
|
|
3221
|
+
|
|
3222
|
+
result = await client.list_contexts(
|
|
3223
|
+
limit=limit,
|
|
3224
|
+
offset=offset,
|
|
3225
|
+
tags=tags,
|
|
3226
|
+
agent_source=agent_source
|
|
3227
|
+
)
|
|
3228
|
+
|
|
3229
|
+
contexts = result.get("contexts", [])
|
|
3230
|
+
pagination = result.get("pagination", {})
|
|
3231
|
+
|
|
3232
|
+
if not contexts:
|
|
3233
|
+
response = "📭 **No Contexts Found**\n\n"
|
|
3234
|
+
if tags or agent_source:
|
|
3235
|
+
response += "No contexts match your filters.\n\n"
|
|
3236
|
+
else:
|
|
3237
|
+
response += "You haven't saved any contexts yet.\n\n"
|
|
3238
|
+
|
|
3239
|
+
response += "**Get started:**\n"
|
|
3240
|
+
response += "• Use `save_context` to save a conversation for cross-agent sharing\n"
|
|
3241
|
+
response += "• Perfect for handoffs between Cursor and Claude Code!"
|
|
3242
|
+
|
|
3243
|
+
return [TextContent(type="text", text=response)]
|
|
3244
|
+
|
|
3245
|
+
# Format the response
|
|
3246
|
+
response = f"📚 **Your Conversation Contexts** ({pagination.get('total', len(contexts))} total)\n\n"
|
|
3247
|
+
|
|
3248
|
+
for i, context in enumerate(contexts, offset + 1):
|
|
3249
|
+
created_at = context.get('created_at', '')
|
|
3250
|
+
if created_at:
|
|
3251
|
+
# Format datetime for better readability
|
|
3252
|
+
try:
|
|
3253
|
+
from datetime import datetime
|
|
3254
|
+
dt = datetime.fromisoformat(created_at.replace('Z', '+00:00'))
|
|
3255
|
+
formatted_date = dt.strftime('%Y-%m-%d %H:%M UTC')
|
|
3256
|
+
except:
|
|
3257
|
+
formatted_date = created_at
|
|
3258
|
+
else:
|
|
3259
|
+
formatted_date = 'Unknown'
|
|
3260
|
+
|
|
3261
|
+
response += f"**{i}. {context['title']}**\n"
|
|
3262
|
+
response += f" 🆔 ID: `{context['id']}`\n"
|
|
3263
|
+
response += f" 🤖 Source: {context['agent_source']}\n"
|
|
3264
|
+
response += f" 📅 Created: {formatted_date}\n"
|
|
3265
|
+
response += f" 📝 Summary: {context['summary'][:100]}{'...' if len(context['summary']) > 100 else ''}\n"
|
|
3266
|
+
if context.get('tags'):
|
|
3267
|
+
response += f" 🏷️ Tags: {', '.join(context['tags'])}\n"
|
|
3268
|
+
response += "\n"
|
|
3269
|
+
|
|
3270
|
+
# Add pagination info
|
|
3271
|
+
if pagination.get('has_more'):
|
|
3272
|
+
next_offset = offset + limit
|
|
3273
|
+
response += f"📄 **Pagination:** Showing {offset + 1}-{offset + len(contexts)} of {pagination.get('total')}\n"
|
|
3274
|
+
response += f" Use `list_contexts(offset={next_offset})` for next page\n"
|
|
3275
|
+
|
|
3276
|
+
response += "\n**Actions:**\n"
|
|
3277
|
+
response += "• `retrieve_context(context_id)` - Get full context\n"
|
|
3278
|
+
response += "• `search_contexts(query)` - Search contexts\n"
|
|
3279
|
+
response += "• `delete_context(context_id)` - Remove context"
|
|
3280
|
+
|
|
3281
|
+
return [TextContent(type="text", text=response)]
|
|
3282
|
+
|
|
3283
|
+
except APIError as e:
|
|
3284
|
+
logger.error(f"API Error listing contexts: {e}")
|
|
3285
|
+
return [TextContent(type="text", text=f"❌ API Error: {str(e)}")]
|
|
3286
|
+
except Exception as e:
|
|
3287
|
+
logger.error(f"Error listing contexts: {e}")
|
|
3288
|
+
return [TextContent(type="text", text=f"❌ Error listing contexts: {str(e)}")]
|
|
3289
|
+
|
|
3290
|
+
@mcp.tool()
|
|
3291
|
+
async def retrieve_context(context_id: str) -> List[TextContent]:
|
|
3292
|
+
"""
|
|
3293
|
+
Retrieve a specific conversation context by ID.
|
|
3294
|
+
|
|
3295
|
+
Use this tool to get the full conversation context that was saved by
|
|
3296
|
+
another agent. Perfect for getting strategic context from Cursor
|
|
3297
|
+
when working in Claude Code.
|
|
3298
|
+
|
|
3299
|
+
Args:
|
|
3300
|
+
context_id: The unique ID of the context to retrieve
|
|
3301
|
+
|
|
3302
|
+
Returns:
|
|
3303
|
+
Full conversation context with metadata
|
|
3304
|
+
|
|
3305
|
+
Example:
|
|
3306
|
+
retrieve_context("550e8400-e29b-41d4-a716-446655440000")
|
|
3307
|
+
"""
|
|
3308
|
+
try:
|
|
3309
|
+
if not context_id or not context_id.strip():
|
|
3310
|
+
return [TextContent(type="text", text="❌ Error: Context ID is required")]
|
|
3311
|
+
|
|
3312
|
+
client = await ensure_api_client()
|
|
3313
|
+
|
|
3314
|
+
context = await client.get_context(context_id.strip())
|
|
3315
|
+
|
|
3316
|
+
if not context:
|
|
3317
|
+
return [TextContent(
|
|
3318
|
+
type="text",
|
|
3319
|
+
text=f"❌ **Context Not Found**\n\n"
|
|
3320
|
+
f"Context ID `{context_id}` was not found.\n\n"
|
|
3321
|
+
f"**Possible reasons:**\n"
|
|
3322
|
+
f"• The context ID is incorrect\n"
|
|
3323
|
+
f"• The context belongs to a different user\n"
|
|
3324
|
+
f"• The context has been deleted\n\n"
|
|
3325
|
+
f"Use `list_contexts()` to see your available contexts."
|
|
3326
|
+
)]
|
|
3327
|
+
|
|
3328
|
+
# Format the context display
|
|
3329
|
+
created_at = context.get('created_at', '')
|
|
3330
|
+
if created_at:
|
|
3331
|
+
try:
|
|
3332
|
+
from datetime import datetime
|
|
3333
|
+
dt = datetime.fromisoformat(created_at.replace('Z', '+00:00'))
|
|
3334
|
+
formatted_date = dt.strftime('%Y-%m-%d %H:%M UTC')
|
|
3335
|
+
except:
|
|
3336
|
+
formatted_date = created_at
|
|
3337
|
+
else:
|
|
3338
|
+
formatted_date = 'Unknown'
|
|
3339
|
+
|
|
3340
|
+
updated_at = context.get('updated_at', '')
|
|
3341
|
+
if updated_at:
|
|
3342
|
+
try:
|
|
3343
|
+
from datetime import datetime
|
|
3344
|
+
dt = datetime.fromisoformat(updated_at.replace('Z', '+00:00'))
|
|
3345
|
+
formatted_updated = dt.strftime('%Y-%m-%d %H:%M UTC')
|
|
3346
|
+
except:
|
|
3347
|
+
formatted_updated = updated_at
|
|
3348
|
+
else:
|
|
3349
|
+
formatted_updated = None
|
|
3350
|
+
|
|
3351
|
+
response = f"📋 **Context: {context['title']}**\n\n"
|
|
3352
|
+
response += f"🆔 **ID:** `{context['id']}`\n"
|
|
3353
|
+
response += f"🤖 **Source Agent:** {context['agent_source']}\n"
|
|
3354
|
+
response += f"📅 **Created:** {formatted_date}\n"
|
|
3355
|
+
if formatted_updated:
|
|
3356
|
+
response += f"🔄 **Updated:** {formatted_updated}\n"
|
|
3357
|
+
|
|
3358
|
+
if context.get('tags'):
|
|
3359
|
+
response += f"🏷️ **Tags:** {', '.join(context['tags'])}\n"
|
|
3360
|
+
|
|
3361
|
+
response += f"\n📝 **Summary:**\n{context['summary']}\n\n"
|
|
3362
|
+
|
|
3363
|
+
# Add metadata if available
|
|
3364
|
+
metadata = context.get('metadata', {})
|
|
3365
|
+
if metadata:
|
|
3366
|
+
response += f"📊 **Metadata:**\n"
|
|
3367
|
+
for key, value in metadata.items():
|
|
3368
|
+
if isinstance(value, list):
|
|
3369
|
+
response += f"• **{key}:** {', '.join(map(str, value))}\n"
|
|
3370
|
+
else:
|
|
3371
|
+
response += f"• **{key}:** {value}\n"
|
|
3372
|
+
response += "\n"
|
|
3373
|
+
|
|
3374
|
+
response += f"📄 **Full Context:**\n\n{context['content']}\n\n"
|
|
3375
|
+
|
|
3376
|
+
response += f"---\n"
|
|
3377
|
+
response += f"💡 **Tips:**\n"
|
|
3378
|
+
response += f"• This context was created by {context['agent_source']}\n"
|
|
3379
|
+
response += f"• Use this information to understand the strategic planning\n"
|
|
3380
|
+
response += f"• You can now implement based on this context!"
|
|
3381
|
+
|
|
3382
|
+
return [TextContent(type="text", text=response)]
|
|
3383
|
+
|
|
3384
|
+
except APIError as e:
|
|
3385
|
+
logger.error(f"API Error retrieving context: {e}")
|
|
3386
|
+
return [TextContent(type="text", text=f"❌ API Error: {str(e)}")]
|
|
3387
|
+
except Exception as e:
|
|
3388
|
+
logger.error(f"Error retrieving context: {e}")
|
|
3389
|
+
return [TextContent(type="text", text=f"❌ Error retrieving context: {str(e)}")]
|
|
3390
|
+
|
|
3391
|
+
@mcp.tool()
|
|
3392
|
+
async def search_contexts(
|
|
3393
|
+
query: str,
|
|
3394
|
+
limit: int = 20,
|
|
3395
|
+
tags: Optional[str] = None,
|
|
3396
|
+
agent_source: Optional[str] = None
|
|
3397
|
+
) -> List[TextContent]:
|
|
3398
|
+
"""
|
|
3399
|
+
Search conversation contexts by content, title, or summary.
|
|
3400
|
+
|
|
3401
|
+
Perfect for finding relevant contexts when you remember part of the
|
|
3402
|
+
conversation but not the exact context ID.
|
|
3403
|
+
|
|
3404
|
+
Args:
|
|
3405
|
+
query: Search query to match against title, summary, content, and tags
|
|
3406
|
+
limit: Maximum number of results to return (1-100, default: 20)
|
|
3407
|
+
tags: Comma-separated tags to filter by (optional)
|
|
3408
|
+
agent_source: Filter by specific agent source (optional)
|
|
3409
|
+
|
|
3410
|
+
Returns:
|
|
3411
|
+
Search results with matching contexts
|
|
3412
|
+
|
|
3413
|
+
Examples:
|
|
3414
|
+
- search_contexts("streaming AI SDK")
|
|
3415
|
+
- search_contexts("authentication", tags="security,implementation")
|
|
3416
|
+
- search_contexts("database", agent_source="cursor")
|
|
3417
|
+
"""
|
|
3418
|
+
try:
|
|
3419
|
+
# Validate parameters
|
|
3420
|
+
if not query or not query.strip():
|
|
3421
|
+
return [TextContent(type="text", text="❌ Error: Search query is required")]
|
|
3422
|
+
|
|
3423
|
+
if limit < 1 or limit > 100:
|
|
3424
|
+
return [TextContent(type="text", text="❌ Error: Limit must be between 1 and 100")]
|
|
3425
|
+
|
|
3426
|
+
client = await ensure_api_client()
|
|
3427
|
+
|
|
3428
|
+
result = await client.search_contexts(
|
|
3429
|
+
query=query.strip(),
|
|
3430
|
+
limit=limit,
|
|
3431
|
+
tags=tags,
|
|
3432
|
+
agent_source=agent_source
|
|
3433
|
+
)
|
|
3434
|
+
|
|
3435
|
+
contexts = result.get("contexts", [])
|
|
3436
|
+
|
|
3437
|
+
if not contexts:
|
|
3438
|
+
response = f"🔍 **No Results Found**\n\n"
|
|
3439
|
+
response += f"No contexts match your search query: \"{query}\"\n\n"
|
|
3440
|
+
|
|
3441
|
+
if tags or agent_source:
|
|
3442
|
+
response += f"**Active filters:**\n"
|
|
3443
|
+
if tags:
|
|
3444
|
+
response += f"• Tags: {tags}\n"
|
|
3445
|
+
if agent_source:
|
|
3446
|
+
response += f"• Agent: {agent_source}\n"
|
|
3447
|
+
response += "\n"
|
|
3448
|
+
|
|
3449
|
+
response += f"**Suggestions:**\n"
|
|
3450
|
+
response += f"• Try different keywords\n"
|
|
3451
|
+
response += f"• Remove filters to broaden search\n"
|
|
3452
|
+
response += f"• Use `list_contexts()` to see all contexts"
|
|
3453
|
+
|
|
3454
|
+
return [TextContent(type="text", text=response)]
|
|
3455
|
+
|
|
3456
|
+
# Format search results
|
|
3457
|
+
response = f"🔍 **Search Results for \"{query}\"** ({len(contexts)} found)\n\n"
|
|
3458
|
+
|
|
3459
|
+
for i, context in enumerate(contexts, 1):
|
|
3460
|
+
created_at = context.get('created_at', '')
|
|
3461
|
+
if created_at:
|
|
3462
|
+
try:
|
|
3463
|
+
from datetime import datetime
|
|
3464
|
+
dt = datetime.fromisoformat(created_at.replace('Z', '+00:00'))
|
|
3465
|
+
formatted_date = dt.strftime('%Y-%m-%d %H:%M UTC')
|
|
3466
|
+
except:
|
|
3467
|
+
formatted_date = created_at
|
|
3468
|
+
else:
|
|
3469
|
+
formatted_date = 'Unknown'
|
|
3470
|
+
|
|
3471
|
+
response += f"**{i}. {context['title']}**\n"
|
|
3472
|
+
response += f" 🆔 ID: `{context['id']}`\n"
|
|
3473
|
+
response += f" 🤖 Source: {context['agent_source']}\n"
|
|
3474
|
+
response += f" 📅 Created: {formatted_date}\n"
|
|
3475
|
+
response += f" 📝 Summary: {context['summary'][:150]}{'...' if len(context['summary']) > 150 else ''}\n"
|
|
3476
|
+
|
|
3477
|
+
if context.get('tags'):
|
|
3478
|
+
response += f" 🏷️ Tags: {', '.join(context['tags'])}\n"
|
|
3479
|
+
|
|
3480
|
+
response += "\n"
|
|
3481
|
+
|
|
3482
|
+
response += f"**Actions:**\n"
|
|
3483
|
+
response += f"• `retrieve_context(context_id)` - Get full context\n"
|
|
3484
|
+
response += f"• Refine search with different keywords\n"
|
|
3485
|
+
response += f"• Use tags or agent filters for better results"
|
|
3486
|
+
|
|
3487
|
+
return [TextContent(type="text", text=response)]
|
|
3488
|
+
|
|
3489
|
+
except APIError as e:
|
|
3490
|
+
logger.error(f"API Error searching contexts: {e}")
|
|
3491
|
+
return [TextContent(type="text", text=f"❌ API Error: {str(e)}")]
|
|
3492
|
+
except Exception as e:
|
|
3493
|
+
logger.error(f"Error searching contexts: {e}")
|
|
3494
|
+
return [TextContent(type="text", text=f"❌ Error searching contexts: {str(e)}")]
|
|
3495
|
+
|
|
3496
|
+
@mcp.tool()
|
|
3497
|
+
async def update_context(
|
|
3498
|
+
context_id: str,
|
|
3499
|
+
title: Optional[str] = None,
|
|
3500
|
+
summary: Optional[str] = None,
|
|
3501
|
+
content: Optional[str] = None,
|
|
3502
|
+
tags: Optional[List[str]] = None,
|
|
3503
|
+
metadata: Optional[dict] = None
|
|
3504
|
+
) -> List[TextContent]:
|
|
3505
|
+
"""
|
|
3506
|
+
Update an existing conversation context.
|
|
3507
|
+
|
|
3508
|
+
Args:
|
|
3509
|
+
context_id: The unique ID of the context to update
|
|
3510
|
+
title: Updated title (optional)
|
|
3511
|
+
summary: Updated summary (optional)
|
|
3512
|
+
content: Updated content (optional)
|
|
3513
|
+
tags: Updated tags list (optional)
|
|
3514
|
+
metadata: Updated metadata (optional)
|
|
3515
|
+
|
|
3516
|
+
Returns:
|
|
3517
|
+
Confirmation of successful update
|
|
3518
|
+
|
|
3519
|
+
Example:
|
|
3520
|
+
update_context(
|
|
3521
|
+
context_id="550e8400-e29b-41d4-a716-446655440000",
|
|
3522
|
+
title="Updated: Streaming AI SDK Implementation",
|
|
3523
|
+
tags=["streaming", "ai-sdk", "completed"]
|
|
3524
|
+
)
|
|
3525
|
+
"""
|
|
3526
|
+
try:
|
|
3527
|
+
if not context_id or not context_id.strip():
|
|
3528
|
+
return [TextContent(type="text", text="❌ Error: Context ID is required")]
|
|
3529
|
+
|
|
3530
|
+
# Check that at least one field is being updated
|
|
3531
|
+
if not any([title, summary, content, tags is not None, metadata is not None]):
|
|
3532
|
+
return [TextContent(
|
|
3533
|
+
type="text",
|
|
3534
|
+
text="❌ Error: At least one field must be provided for update"
|
|
3535
|
+
)]
|
|
3536
|
+
|
|
3537
|
+
# Validate fields if provided
|
|
3538
|
+
if title is not None and (not title.strip() or len(title) > 200):
|
|
3539
|
+
return [TextContent(
|
|
3540
|
+
type="text",
|
|
3541
|
+
text="❌ Error: Title must be 1-200 characters"
|
|
3542
|
+
)]
|
|
3543
|
+
|
|
3544
|
+
if summary is not None and (len(summary) < 10 or len(summary) > 1000):
|
|
3545
|
+
return [TextContent(
|
|
3546
|
+
type="text",
|
|
3547
|
+
text="❌ Error: Summary must be 10-1000 characters"
|
|
3548
|
+
)]
|
|
3549
|
+
|
|
3550
|
+
if content is not None and len(content) < 50:
|
|
3551
|
+
return [TextContent(
|
|
3552
|
+
type="text",
|
|
3553
|
+
text="❌ Error: Content must be at least 50 characters"
|
|
3554
|
+
)]
|
|
3555
|
+
|
|
3556
|
+
if tags is not None and len(tags) > 10:
|
|
3557
|
+
return [TextContent(
|
|
3558
|
+
type="text",
|
|
3559
|
+
text="❌ Error: Maximum 10 tags allowed"
|
|
3560
|
+
)]
|
|
3561
|
+
|
|
3562
|
+
client = await ensure_api_client()
|
|
3563
|
+
|
|
3564
|
+
result = await client.update_context(
|
|
3565
|
+
context_id=context_id.strip(),
|
|
3566
|
+
title=title.strip() if title else None,
|
|
3567
|
+
summary=summary.strip() if summary else None,
|
|
3568
|
+
content=content,
|
|
3569
|
+
tags=tags,
|
|
3570
|
+
metadata=metadata
|
|
3571
|
+
)
|
|
3572
|
+
|
|
3573
|
+
if not result:
|
|
3574
|
+
return [TextContent(
|
|
3575
|
+
type="text",
|
|
3576
|
+
text=f"❌ Error: Context with ID `{context_id}` not found"
|
|
3577
|
+
)]
|
|
3578
|
+
|
|
3579
|
+
# List updated fields
|
|
3580
|
+
updated_fields = []
|
|
3581
|
+
if title is not None:
|
|
3582
|
+
updated_fields.append("title")
|
|
3583
|
+
if summary is not None:
|
|
3584
|
+
updated_fields.append("summary")
|
|
3585
|
+
if content is not None:
|
|
3586
|
+
updated_fields.append("content")
|
|
3587
|
+
if tags is not None:
|
|
3588
|
+
updated_fields.append("tags")
|
|
3589
|
+
if metadata is not None:
|
|
3590
|
+
updated_fields.append("metadata")
|
|
3591
|
+
|
|
3592
|
+
response = f"✅ **Context Updated Successfully!**\n\n"
|
|
3593
|
+
response += f"🆔 **Context ID:** `{context_id}`\n"
|
|
3594
|
+
response += f"📝 **Title:** {result['title']}\n"
|
|
3595
|
+
response += f"🔄 **Updated Fields:** {', '.join(updated_fields)}\n"
|
|
3596
|
+
response += f"🤖 **Source Agent:** {result['agent_source']}\n\n"
|
|
3597
|
+
|
|
3598
|
+
response += f"**Current Status:**\n"
|
|
3599
|
+
response += f"• **Tags:** {', '.join(result['tags']) if result.get('tags') else 'None'}\n"
|
|
3600
|
+
response += f"• **Content Length:** {len(result['content']):,} characters\n\n"
|
|
3601
|
+
|
|
3602
|
+
response += f"Use `retrieve_context('{context_id}')` to see the full updated context."
|
|
3603
|
+
|
|
3604
|
+
return [TextContent(type="text", text=response)]
|
|
3605
|
+
|
|
3606
|
+
except APIError as e:
|
|
3607
|
+
logger.error(f"API Error updating context: {e}")
|
|
3608
|
+
return [TextContent(type="text", text=f"❌ API Error: {str(e)}")]
|
|
3609
|
+
except Exception as e:
|
|
3610
|
+
logger.error(f"Error updating context: {e}")
|
|
3611
|
+
return [TextContent(type="text", text=f"❌ Error updating context: {str(e)}")]
|
|
3612
|
+
|
|
3613
|
+
@mcp.tool()
|
|
3614
|
+
async def delete_context(context_id: str) -> List[TextContent]:
|
|
3615
|
+
"""
|
|
3616
|
+
Delete a conversation context.
|
|
3617
|
+
|
|
3618
|
+
Args:
|
|
3619
|
+
context_id: The unique ID of the context to delete
|
|
3620
|
+
|
|
3621
|
+
Returns:
|
|
3622
|
+
Confirmation of successful deletion
|
|
3623
|
+
|
|
3624
|
+
Example:
|
|
3625
|
+
delete_context("550e8400-e29b-41d4-a716-446655440000")
|
|
3626
|
+
"""
|
|
3627
|
+
try:
|
|
3628
|
+
if not context_id or not context_id.strip():
|
|
3629
|
+
return [TextContent(type="text", text="❌ Error: Context ID is required")]
|
|
3630
|
+
|
|
3631
|
+
client = await ensure_api_client()
|
|
3632
|
+
|
|
3633
|
+
success = await client.delete_context(context_id.strip())
|
|
3634
|
+
|
|
3635
|
+
if success:
|
|
3636
|
+
return [TextContent(
|
|
3637
|
+
type="text",
|
|
3638
|
+
text=f"✅ **Context Deleted Successfully!**\n\n"
|
|
3639
|
+
f"🆔 **Context ID:** `{context_id}`\n\n"
|
|
3640
|
+
f"The context has been permanently removed from your account.\n"
|
|
3641
|
+
f"This action cannot be undone.\n\n"
|
|
3642
|
+
f"Use `list_contexts()` to see your remaining contexts."
|
|
3643
|
+
)]
|
|
3644
|
+
else:
|
|
3645
|
+
return [TextContent(
|
|
3646
|
+
type="text",
|
|
3647
|
+
text=f"❌ **Context Not Found**\n\n"
|
|
3648
|
+
f"Context ID `{context_id}` was not found or has already been deleted.\n\n"
|
|
3649
|
+
f"Use `list_contexts()` to see your available contexts."
|
|
3650
|
+
)]
|
|
3651
|
+
|
|
3652
|
+
except APIError as e:
|
|
3653
|
+
logger.error(f"API Error deleting context: {e}")
|
|
3654
|
+
return [TextContent(type="text", text=f"❌ API Error: {str(e)}")]
|
|
3655
|
+
except Exception as e:
|
|
3656
|
+
logger.error(f"Error deleting context: {e}")
|
|
3657
|
+
return [TextContent(type="text", text=f"❌ Error deleting context: {str(e)}")]
|
|
3658
|
+
|
|
2982
3659
|
# Resources
|
|
2983
3660
|
|
|
2984
3661
|
# Note: FastMCP doesn't have list_resources or read_resource decorators
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{nia_mcp_server-1.0.21 → nia_mcp_server-1.0.23}/src/nia_mcp_server/assets/rules/claude_rules.md
RENAMED
|
File without changes
|
{nia_mcp_server-1.0.21 → nia_mcp_server-1.0.23}/src/nia_mcp_server/assets/rules/cursor_rules.md
RENAMED
|
File without changes
|
{nia_mcp_server-1.0.21 → nia_mcp_server-1.0.23}/src/nia_mcp_server/assets/rules/nia_rules.md
RENAMED
|
File without changes
|
{nia_mcp_server-1.0.21 → nia_mcp_server-1.0.23}/src/nia_mcp_server/assets/rules/vscode_rules.md
RENAMED
|
File without changes
|
{nia_mcp_server-1.0.21 → nia_mcp_server-1.0.23}/src/nia_mcp_server/assets/rules/windsurf_rules.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|