fenix-mcp 2.0.0__tar.gz → 2.2.0__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.
Files changed (42) hide show
  1. {fenix_mcp-2.0.0 → fenix_mcp-2.2.0}/PKG-INFO +1 -1
  2. {fenix_mcp-2.0.0 → fenix_mcp-2.2.0}/fenix_mcp/__init__.py +1 -1
  3. fenix_mcp-2.2.0/fenix_mcp/application/prompt_base.py +76 -0
  4. fenix_mcp-2.2.0/fenix_mcp/application/prompt_registry.py +39 -0
  5. fenix_mcp-2.2.0/fenix_mcp/application/prompts/__init__.py +28 -0
  6. fenix_mcp-2.2.0/fenix_mcp/application/prompts/memory.py +60 -0
  7. fenix_mcp-2.2.0/fenix_mcp/application/prompts/mine.py +37 -0
  8. fenix_mcp-2.2.0/fenix_mcp/application/prompts/rules.py +37 -0
  9. {fenix_mcp-2.0.0 → fenix_mcp-2.2.0}/fenix_mcp/infrastructure/fenix_api/client.py +6 -2
  10. fenix_mcp-2.2.0/fenix_mcp/interface/mcp_server.py +196 -0
  11. {fenix_mcp-2.0.0 → fenix_mcp-2.2.0}/fenix_mcp.egg-info/PKG-INFO +1 -1
  12. {fenix_mcp-2.0.0 → fenix_mcp-2.2.0}/fenix_mcp.egg-info/SOURCES.txt +6 -0
  13. fenix_mcp-2.0.0/fenix_mcp/interface/mcp_server.py +0 -87
  14. {fenix_mcp-2.0.0 → fenix_mcp-2.2.0}/README.md +0 -0
  15. {fenix_mcp-2.0.0 → fenix_mcp-2.2.0}/fenix_mcp/application/presenters.py +0 -0
  16. {fenix_mcp-2.0.0 → fenix_mcp-2.2.0}/fenix_mcp/application/tool_base.py +0 -0
  17. {fenix_mcp-2.0.0 → fenix_mcp-2.2.0}/fenix_mcp/application/tool_registry.py +0 -0
  18. {fenix_mcp-2.0.0 → fenix_mcp-2.2.0}/fenix_mcp/application/tools/__init__.py +0 -0
  19. {fenix_mcp-2.0.0 → fenix_mcp-2.2.0}/fenix_mcp/application/tools/health.py +0 -0
  20. {fenix_mcp-2.0.0 → fenix_mcp-2.2.0}/fenix_mcp/application/tools/initialize.py +0 -0
  21. {fenix_mcp-2.0.0 → fenix_mcp-2.2.0}/fenix_mcp/application/tools/intelligence.py +0 -0
  22. {fenix_mcp-2.0.0 → fenix_mcp-2.2.0}/fenix_mcp/application/tools/knowledge.py +0 -0
  23. {fenix_mcp-2.0.0 → fenix_mcp-2.2.0}/fenix_mcp/application/tools/productivity.py +0 -0
  24. {fenix_mcp-2.0.0 → fenix_mcp-2.2.0}/fenix_mcp/application/tools/user_config.py +0 -0
  25. {fenix_mcp-2.0.0 → fenix_mcp-2.2.0}/fenix_mcp/domain/initialization.py +0 -0
  26. {fenix_mcp-2.0.0 → fenix_mcp-2.2.0}/fenix_mcp/domain/intelligence.py +0 -0
  27. {fenix_mcp-2.0.0 → fenix_mcp-2.2.0}/fenix_mcp/domain/knowledge.py +0 -0
  28. {fenix_mcp-2.0.0 → fenix_mcp-2.2.0}/fenix_mcp/domain/productivity.py +0 -0
  29. {fenix_mcp-2.0.0 → fenix_mcp-2.2.0}/fenix_mcp/domain/user_config.py +0 -0
  30. {fenix_mcp-2.0.0 → fenix_mcp-2.2.0}/fenix_mcp/infrastructure/config.py +0 -0
  31. {fenix_mcp-2.0.0 → fenix_mcp-2.2.0}/fenix_mcp/infrastructure/context.py +0 -0
  32. {fenix_mcp-2.0.0 → fenix_mcp-2.2.0}/fenix_mcp/infrastructure/http_client.py +0 -0
  33. {fenix_mcp-2.0.0 → fenix_mcp-2.2.0}/fenix_mcp/infrastructure/logging.py +0 -0
  34. {fenix_mcp-2.0.0 → fenix_mcp-2.2.0}/fenix_mcp/infrastructure/request_context.py +0 -0
  35. {fenix_mcp-2.0.0 → fenix_mcp-2.2.0}/fenix_mcp/interface/transports.py +0 -0
  36. {fenix_mcp-2.0.0 → fenix_mcp-2.2.0}/fenix_mcp/main.py +0 -0
  37. {fenix_mcp-2.0.0 → fenix_mcp-2.2.0}/fenix_mcp.egg-info/dependency_links.txt +0 -0
  38. {fenix_mcp-2.0.0 → fenix_mcp-2.2.0}/fenix_mcp.egg-info/entry_points.txt +0 -0
  39. {fenix_mcp-2.0.0 → fenix_mcp-2.2.0}/fenix_mcp.egg-info/requires.txt +0 -0
  40. {fenix_mcp-2.0.0 → fenix_mcp-2.2.0}/fenix_mcp.egg-info/top_level.txt +0 -0
  41. {fenix_mcp-2.0.0 → fenix_mcp-2.2.0}/pyproject.toml +0 -0
  42. {fenix_mcp-2.0.0 → fenix_mcp-2.2.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fenix-mcp
3
- Version: 2.0.0
3
+ Version: 2.2.0
4
4
  Summary: Fênix Cloud MCP server implemented in Python
5
5
  Author: Fenix Inc
6
6
  Requires-Python: >=3.10
@@ -8,4 +8,4 @@ Fênix Cloud MCP Server (Python edition).
8
8
  __all__ = ["__version__"]
9
9
 
10
10
 
11
- __version__ = "2.0.0"
11
+ __version__ = "2.2.0"
@@ -0,0 +1,76 @@
1
+ # SPDX-License-Identifier: MIT
2
+ """Base abstractions for MCP prompts."""
3
+
4
+ from __future__ import annotations
5
+
6
+ from abc import ABC, abstractmethod
7
+ from dataclasses import dataclass, field
8
+ from typing import Any, Dict, List, Optional
9
+
10
+
11
+ @dataclass
12
+ class PromptArgument:
13
+ """Defines an argument that a prompt accepts."""
14
+
15
+ name: str
16
+ description: str
17
+ required: bool = False
18
+
19
+
20
+ @dataclass
21
+ class PromptMessage:
22
+ """A single message in a prompt response."""
23
+
24
+ role: str # "user" or "assistant"
25
+ text: str
26
+
27
+ def to_dict(self) -> Dict[str, Any]:
28
+ return {
29
+ "role": self.role,
30
+ "content": {"type": "text", "text": self.text},
31
+ }
32
+
33
+
34
+ @dataclass
35
+ class PromptResult:
36
+ """Result returned by prompts/get."""
37
+
38
+ messages: List[PromptMessage] = field(default_factory=list)
39
+ description: Optional[str] = None
40
+
41
+ def to_dict(self) -> Dict[str, Any]:
42
+ result: Dict[str, Any] = {
43
+ "messages": [m.to_dict() for m in self.messages],
44
+ }
45
+ if self.description:
46
+ result["description"] = self.description
47
+ return result
48
+
49
+
50
+ class Prompt(ABC):
51
+ """Interface implemented by all prompts."""
52
+
53
+ name: str
54
+ description: str
55
+ arguments: List[PromptArgument] = []
56
+
57
+ def schema(self) -> Dict[str, Any]:
58
+ """Return JSON schema describing the prompt."""
59
+ result: Dict[str, Any] = {
60
+ "name": self.name,
61
+ "description": self.description,
62
+ }
63
+ if self.arguments:
64
+ result["arguments"] = [
65
+ {
66
+ "name": arg.name,
67
+ "description": arg.description,
68
+ "required": arg.required,
69
+ }
70
+ for arg in self.arguments
71
+ ]
72
+ return result
73
+
74
+ @abstractmethod
75
+ def get_messages(self, arguments: Dict[str, Any]) -> PromptResult:
76
+ """Generate prompt messages based on provided arguments."""
@@ -0,0 +1,39 @@
1
+ # SPDX-License-Identifier: MIT
2
+ """Registry that keeps the mapping between prompt names and instances."""
3
+
4
+ from __future__ import annotations
5
+
6
+ from typing import Any, Dict, Iterable, List
7
+
8
+ from fenix_mcp.application.prompt_base import Prompt, PromptResult
9
+
10
+
11
+ class PromptRegistry:
12
+ """Lookup table for prompt execution."""
13
+
14
+ def __init__(self, prompts: Iterable[Prompt]):
15
+ self._prompts: Dict[str, Prompt] = {}
16
+ for prompt in prompts:
17
+ if prompt.name in self._prompts:
18
+ raise ValueError(f"Duplicate prompt name detected: {prompt.name}")
19
+ self._prompts[prompt.name] = prompt
20
+
21
+ def list_definitions(self) -> List[dict]:
22
+ """Return list of prompt schemas for prompts/list."""
23
+ return [prompt.schema() for prompt in self._prompts.values()]
24
+
25
+ def get(self, name: str, arguments: Dict[str, Any]) -> PromptResult:
26
+ """Get prompt messages for prompts/get."""
27
+ try:
28
+ prompt = self._prompts[name]
29
+ except KeyError as exc:
30
+ raise KeyError(f"Unknown prompt '{name}'") from exc
31
+ return prompt.get_messages(arguments)
32
+
33
+
34
+ def build_default_prompt_registry() -> PromptRegistry:
35
+ """Build the default prompt registry with all available prompts."""
36
+ from fenix_mcp.application.prompts import build_prompts
37
+
38
+ prompts = build_prompts()
39
+ return PromptRegistry(prompts)
@@ -0,0 +1,28 @@
1
+ # SPDX-License-Identifier: MIT
2
+ """Prompt implementations for Fenix MCP server."""
3
+
4
+ from __future__ import annotations
5
+
6
+ from typing import List
7
+
8
+ from fenix_mcp.application.prompt_base import Prompt
9
+ from fenix_mcp.application.prompts.memory import FenixMemoryPrompt
10
+ from fenix_mcp.application.prompts.mine import FenixMinePrompt
11
+ from fenix_mcp.application.prompts.rules import FenixRulesPrompt
12
+
13
+
14
+ def build_prompts() -> List[Prompt]:
15
+ """Build and return all available prompts."""
16
+ return [
17
+ FenixMemoryPrompt(),
18
+ FenixMinePrompt(),
19
+ FenixRulesPrompt(),
20
+ ]
21
+
22
+
23
+ __all__ = [
24
+ "build_prompts",
25
+ "FenixMemoryPrompt",
26
+ "FenixMinePrompt",
27
+ "FenixRulesPrompt",
28
+ ]
@@ -0,0 +1,60 @@
1
+ # SPDX-License-Identifier: MIT
2
+ """Fenix Memory prompt - save content to semantic memory."""
3
+
4
+ from __future__ import annotations
5
+
6
+ from typing import Any, Dict, List
7
+
8
+ from fenix_mcp.application.prompt_base import (
9
+ Prompt,
10
+ PromptArgument,
11
+ PromptMessage,
12
+ PromptResult,
13
+ )
14
+
15
+
16
+ class FenixMemoryPrompt(Prompt):
17
+ """Prompt to save content to Fenix semantic memory."""
18
+
19
+ name = "fenix-memory"
20
+ description = "Save content to Fenix semantic memory for future reference"
21
+ arguments: List[PromptArgument] = [
22
+ PromptArgument(
23
+ name="content",
24
+ description="The content to save to memory",
25
+ required=True,
26
+ ),
27
+ ]
28
+
29
+ def get_messages(self, arguments: Dict[str, Any]) -> PromptResult:
30
+ content = arguments.get("content", "")
31
+
32
+ if not content:
33
+ return PromptResult(
34
+ messages=[
35
+ PromptMessage(
36
+ role="user",
37
+ text=(
38
+ "I want to save something to Fenix memory but didn't "
39
+ "provide content. Please ask me what I want to save."
40
+ ),
41
+ )
42
+ ]
43
+ )
44
+
45
+ instruction = f"""Save the following content to Fenix semantic memory using \
46
+ the `mcp__fenix__intelligence` tool with `action: memory_save`.
47
+
48
+ **Content to save:**
49
+ {content}
50
+
51
+ **Instructions:**
52
+ 1. Create an appropriate title that summarizes the content
53
+ 2. Generate relevant tags (at least 2-3 tags)
54
+ 3. Use the content as provided for the memory content field
55
+ 4. Confirm once saved successfully"""
56
+
57
+ return PromptResult(
58
+ description="Save content to Fenix semantic memory",
59
+ messages=[PromptMessage(role="user", text=instruction)],
60
+ )
@@ -0,0 +1,37 @@
1
+ # SPDX-License-Identifier: MIT
2
+ """Fenix Mine prompt - list work items assigned to the user."""
3
+
4
+ from __future__ import annotations
5
+
6
+ from typing import Any, Dict, List
7
+
8
+ from fenix_mcp.application.prompt_base import (
9
+ Prompt,
10
+ PromptArgument,
11
+ PromptMessage,
12
+ PromptResult,
13
+ )
14
+
15
+
16
+ class FenixMinePrompt(Prompt):
17
+ """Prompt to list work items assigned to the current user."""
18
+
19
+ name = "fenix-mine"
20
+ description = "List all work items assigned to me"
21
+ arguments: List[PromptArgument] = []
22
+
23
+ def get_messages(self, arguments: Dict[str, Any]) -> PromptResult:
24
+ instruction = """List all work items assigned to me using the \
25
+ `mcp__fenix__knowledge` tool with `action: work_mine`.
26
+
27
+ **Instructions:**
28
+ 1. Fetch my assigned work items
29
+ 2. Present them in a clear, organized format (table or list)
30
+ 3. Include: key, title, status, priority, and type for each item
31
+ 4. Group by status if there are many items (e.g., In Progress, Pending, etc.)
32
+ 5. Highlight any high-priority or urgent items"""
33
+
34
+ return PromptResult(
35
+ description="List work items assigned to me",
36
+ messages=[PromptMessage(role="user", text=instruction)],
37
+ )
@@ -0,0 +1,37 @@
1
+ # SPDX-License-Identifier: MIT
2
+ """Fenix Rules prompt - list team coding rules and standards."""
3
+
4
+ from __future__ import annotations
5
+
6
+ from typing import Any, Dict, List
7
+
8
+ from fenix_mcp.application.prompt_base import (
9
+ Prompt,
10
+ PromptArgument,
11
+ PromptMessage,
12
+ PromptResult,
13
+ )
14
+
15
+
16
+ class FenixRulesPrompt(Prompt):
17
+ """Prompt to list team coding rules and standards."""
18
+
19
+ name = "fenix-rules"
20
+ description = "List team coding rules and standards"
21
+ arguments: List[PromptArgument] = []
22
+
23
+ def get_messages(self, arguments: Dict[str, Any]) -> PromptResult:
24
+ instruction = """List the team's coding rules and standards using the \
25
+ `mcp__fenix__knowledge` tool with `action: rule_list`.
26
+
27
+ **Instructions:**
28
+ 1. Fetch all active team rules
29
+ 2. Present them in a clear, organized format
30
+ 3. Include: rule name, description, and scope for each rule
31
+ 4. If there are many rules, group them by category or scope
32
+ 5. Highlight any rules that are particularly important or frequently referenced"""
33
+
34
+ return PromptResult(
35
+ description="List team coding rules and standards",
36
+ messages=[PromptMessage(role="user", text=instruction)],
37
+ )
@@ -127,8 +127,12 @@ class FenixApiClient:
127
127
  def get_health(self) -> Any:
128
128
  return self._request("GET", "/health")
129
129
 
130
- def get_profile(self) -> Any:
131
- return self._request("GET", "/api/auth/profile")
130
+ def get_profile(self, *, include_documents: bool = False) -> Any:
131
+ """Get user profile. Use include_documents=True to include core and user documents."""
132
+ params = self._build_params(
133
+ optional={"include": "documents" if include_documents else None}
134
+ )
135
+ return self._request("GET", "/api/auth/profile", params=params)
132
136
 
133
137
  # ------------------------------------------------------------------
134
138
  # Core documents (requires PAT authentication)
@@ -0,0 +1,196 @@
1
+ # SPDX-License-Identifier: MIT
2
+ """Lightweight MCP server implementation backed by the tool registry."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import asyncio
7
+ import uuid
8
+ from dataclasses import dataclass, field
9
+ from typing import Any, Dict, Optional
10
+
11
+ from fenix_mcp.application.prompt_registry import (
12
+ PromptRegistry,
13
+ build_default_prompt_registry,
14
+ )
15
+ from fenix_mcp.application.tool_registry import ToolRegistry, build_default_registry
16
+ from fenix_mcp.infrastructure.context import AppContext
17
+
18
+
19
+ class McpServerError(RuntimeError):
20
+ pass
21
+
22
+
23
+ @dataclass
24
+ class SimpleMcpServer:
25
+ context: AppContext
26
+ registry: ToolRegistry
27
+ prompt_registry: PromptRegistry
28
+ session_id: str
29
+ _init_instructions: Optional[str] = field(default=None, repr=False)
30
+
31
+ def set_personal_access_token(self, token: Optional[str]) -> None:
32
+ self.context.api_client.update_token(token)
33
+
34
+ async def _build_auto_init_instructions(self) -> str:
35
+ """Load Fenix context automatically on MCP protocol initialize."""
36
+ api = self.context.api_client
37
+ logger = self.context.logger
38
+
39
+ # Single API call to get profile with documents
40
+ try:
41
+ profile = await asyncio.to_thread(api.get_profile, include_documents=True)
42
+ except Exception as exc:
43
+ logger.warning("Auto-init API call failed: %s", exc)
44
+ profile = None
45
+
46
+ if profile is None:
47
+ profile = {}
48
+
49
+ core_documents = profile.get("coreDocuments") or []
50
+ user_documents = profile.get("userDocuments") or []
51
+
52
+ # Build context summary
53
+ user_info = (profile or {}).get("user") or {}
54
+ tenant_info = (profile or {}).get("tenant") or {}
55
+ team_info = (profile or {}).get("team") or {}
56
+
57
+ lines = [
58
+ "# Fenix Cloud Context (Auto-initialized)",
59
+ "",
60
+ "## User Context",
61
+ f"- **User**: {user_info.get('name', 'Unknown')} (`{user_info.get('id', 'N/A')}`)",
62
+ f"- **Tenant**: {tenant_info.get('name', 'Unknown')} (`{tenant_info.get('id', 'N/A')}`)",
63
+ f"- **Team**: {team_info.get('name', 'Unknown')} (`{team_info.get('id', 'N/A')}`)",
64
+ ]
65
+
66
+ if core_documents:
67
+ lines.extend(["", "## Core Documents"])
68
+ for doc in core_documents:
69
+ name = doc.get("name", "untitled")
70
+ content = doc.get("content", "")
71
+ metadata = doc.get("metadata", "")
72
+ lines.append(f"\n### {name}")
73
+ if content:
74
+ lines.append(content)
75
+ if metadata:
76
+ lines.append(f"\n**Metadata:**\n{metadata}")
77
+
78
+ if user_documents:
79
+ lines.extend(["", "## User Documents"])
80
+ for doc in user_documents:
81
+ name = doc.get("name", "untitled")
82
+ content = doc.get("content", "")
83
+ lines.append(f"\n### {name}")
84
+ if content:
85
+ lines.append(content)
86
+
87
+ lines.extend(
88
+ [
89
+ "",
90
+ "## Available Tools",
91
+ "Use `mcp__fenix__knowledge` for work items, docs, sprints, rules.",
92
+ "Use `mcp__fenix__intelligence` for semantic memory search/save.",
93
+ "Use `mcp__fenix__productivity` for TODOs.",
94
+ ]
95
+ )
96
+
97
+ return "\n".join(lines)
98
+
99
+ async def handle(self, request: Dict[str, Any]) -> Optional[Dict[str, Any]]:
100
+ method = request.get("method")
101
+ request_id = request.get("id")
102
+
103
+ if method == "initialize":
104
+ # Auto-load Fenix context
105
+ try:
106
+ self._init_instructions = await self._build_auto_init_instructions()
107
+ except Exception as exc:
108
+ self.context.logger.warning("Auto-init failed: %s", exc)
109
+ self._init_instructions = None
110
+
111
+ result = {
112
+ "protocolVersion": "2024-11-05",
113
+ "capabilities": {"tools": {}, "prompts": {}, "logging": {}},
114
+ "serverInfo": {"name": "fenix_cloud_mcp_py", "version": "0.1.0"},
115
+ "sessionId": self.session_id,
116
+ }
117
+
118
+ if self._init_instructions:
119
+ result["instructions"] = self._init_instructions
120
+
121
+ return {
122
+ "jsonrpc": "2.0",
123
+ "id": request_id,
124
+ "result": result,
125
+ }
126
+
127
+ if method == "tools/list":
128
+ return {
129
+ "jsonrpc": "2.0",
130
+ "id": request_id,
131
+ "result": {"tools": self.registry.list_definitions()},
132
+ }
133
+
134
+ if method == "tools/call":
135
+ params = request.get("params") or {}
136
+ name = params.get("name")
137
+ arguments = params.get("arguments") or {}
138
+
139
+ if not name:
140
+ raise McpServerError("Missing tool name in tools/call payload")
141
+
142
+ result = await self.registry.execute(name, arguments, self.context)
143
+
144
+ return {"jsonrpc": "2.0", "id": request_id, "result": result}
145
+
146
+ if method == "prompts/list":
147
+ return {
148
+ "jsonrpc": "2.0",
149
+ "id": request_id,
150
+ "result": {"prompts": self.prompt_registry.list_definitions()},
151
+ }
152
+
153
+ if method == "prompts/get":
154
+ params = request.get("params") or {}
155
+ name = params.get("name")
156
+ arguments = params.get("arguments") or {}
157
+
158
+ if not name:
159
+ raise McpServerError("Missing prompt name in prompts/get payload")
160
+
161
+ prompt_result = self.prompt_registry.get(name, arguments)
162
+
163
+ return {
164
+ "jsonrpc": "2.0",
165
+ "id": request_id,
166
+ "result": prompt_result.to_dict(),
167
+ }
168
+
169
+ if method == "notifications/initialized":
170
+ # Notifications do not require a response
171
+ return None
172
+
173
+ if method == "notifications/cancelled":
174
+ # Client cancelled a request - no response needed
175
+ return None
176
+
177
+ if method == "logging/setLevel":
178
+ # Acknowledge log level change request (we don't actually change anything)
179
+ return {
180
+ "jsonrpc": "2.0",
181
+ "id": request_id,
182
+ "result": {},
183
+ }
184
+
185
+ raise McpServerError(f"Unsupported method: {method}")
186
+
187
+
188
+ def build_server(context: AppContext) -> SimpleMcpServer:
189
+ registry = build_default_registry(context)
190
+ prompt_registry = build_default_prompt_registry()
191
+ return SimpleMcpServer(
192
+ context=context,
193
+ registry=registry,
194
+ prompt_registry=prompt_registry,
195
+ session_id=str(uuid.uuid4()),
196
+ )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fenix-mcp
3
- Version: 2.0.0
3
+ Version: 2.2.0
4
4
  Summary: Fênix Cloud MCP server implemented in Python
5
5
  Author: Fenix Inc
6
6
  Requires-Python: >=3.10
@@ -9,8 +9,14 @@ fenix_mcp.egg-info/entry_points.txt
9
9
  fenix_mcp.egg-info/requires.txt
10
10
  fenix_mcp.egg-info/top_level.txt
11
11
  fenix_mcp/application/presenters.py
12
+ fenix_mcp/application/prompt_base.py
13
+ fenix_mcp/application/prompt_registry.py
12
14
  fenix_mcp/application/tool_base.py
13
15
  fenix_mcp/application/tool_registry.py
16
+ fenix_mcp/application/prompts/__init__.py
17
+ fenix_mcp/application/prompts/memory.py
18
+ fenix_mcp/application/prompts/mine.py
19
+ fenix_mcp/application/prompts/rules.py
14
20
  fenix_mcp/application/tools/__init__.py
15
21
  fenix_mcp/application/tools/health.py
16
22
  fenix_mcp/application/tools/initialize.py
@@ -1,87 +0,0 @@
1
- # SPDX-License-Identifier: MIT
2
- """Lightweight MCP server implementation backed by the tool registry."""
3
-
4
- from __future__ import annotations
5
-
6
- import uuid
7
- from dataclasses import dataclass
8
- from typing import Any, Dict, Optional
9
-
10
- from fenix_mcp.application.tool_registry import ToolRegistry, build_default_registry
11
- from fenix_mcp.infrastructure.context import AppContext
12
-
13
-
14
- class McpServerError(RuntimeError):
15
- pass
16
-
17
-
18
- @dataclass(slots=True)
19
- class SimpleMcpServer:
20
- context: AppContext
21
- registry: ToolRegistry
22
- session_id: str
23
-
24
- def set_personal_access_token(self, token: Optional[str]) -> None:
25
- self.context.api_client.update_token(token)
26
-
27
- async def handle(self, request: Dict[str, Any]) -> Optional[Dict[str, Any]]:
28
- method = request.get("method")
29
- request_id = request.get("id")
30
-
31
- if method == "initialize":
32
- return {
33
- "jsonrpc": "2.0",
34
- "id": request_id,
35
- "result": {
36
- "protocolVersion": "2024-11-05",
37
- "capabilities": {"tools": {}, "logging": {}},
38
- "serverInfo": {"name": "fenix_cloud_mcp_py", "version": "0.1.0"},
39
- "sessionId": self.session_id,
40
- },
41
- }
42
-
43
- if method == "tools/list":
44
- return {
45
- "jsonrpc": "2.0",
46
- "id": request_id,
47
- "result": {"tools": self.registry.list_definitions()},
48
- }
49
-
50
- if method == "tools/call":
51
- params = request.get("params") or {}
52
- name = params.get("name")
53
- arguments = params.get("arguments") or {}
54
-
55
- if not name:
56
- raise McpServerError("Missing tool name in tools/call payload")
57
-
58
- result = await self.registry.execute(name, arguments, self.context)
59
-
60
- return {"jsonrpc": "2.0", "id": request_id, "result": result}
61
-
62
- if method == "notifications/initialized":
63
- # Notifications do not require a response
64
- return None
65
-
66
- if method == "notifications/cancelled":
67
- # Client cancelled a request - no response needed
68
- return None
69
-
70
- if method == "logging/setLevel":
71
- # Acknowledge log level change request (we don't actually change anything)
72
- return {
73
- "jsonrpc": "2.0",
74
- "id": request_id,
75
- "result": {},
76
- }
77
-
78
- raise McpServerError(f"Unsupported method: {method}")
79
-
80
-
81
- def build_server(context: AppContext) -> SimpleMcpServer:
82
- registry = build_default_registry(context)
83
- return SimpleMcpServer(
84
- context=context,
85
- registry=registry,
86
- session_id=str(uuid.uuid4()),
87
- )
File without changes
File without changes
File without changes
File without changes