kg-mcp 0.1.8__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.
@@ -0,0 +1,8 @@
1
+ """
2
+ Prompt templates for LLM operations.
3
+ """
4
+
5
+ from kg_mcp.llm.prompts.extractor import get_extractor_prompt
6
+ from kg_mcp.llm.prompts.linker import get_linker_prompt
7
+
8
+ __all__ = ["get_extractor_prompt", "get_linker_prompt"]
@@ -0,0 +1,84 @@
1
+ """
2
+ Extractor prompt template for entity extraction from user text.
3
+ """
4
+
5
+ from typing import List, Optional, Tuple
6
+
7
+
8
+ EXTRACTOR_SYSTEM_PROMPT = """You are an expert at analyzing developer requests and extracting structured information.
9
+ Your task is to analyze the user's message and extract:
10
+ 1. GOALS: What the user wants to achieve (objectives, features, fixes)
11
+ 2. CONSTRAINTS: Limitations or requirements (budget, technology stack, performance, time)
12
+ 3. PREFERENCES: User's coding/architecture preferences (patterns, styles, tools)
13
+ 4. PAIN_POINTS: Problems or frustrations mentioned
14
+ 5. STRATEGIES: Approaches or plans mentioned for solving problems (track Success/Failure outcomes!)
15
+ 6. ACCEPTANCE_CRITERIA: Success conditions mentioned
16
+ 7. CODE_REFERENCES: Any files, functions, classes, or code snippets referenced
17
+ 8. NEXT_ACTIONS: Immediate next steps implied by the request
18
+
19
+ IMPORTANT RULES:
20
+ - Only extract information that is explicitly stated or strongly implied
21
+ - Be precise and concise in descriptions
22
+ - For code_references, extract paths exactly as mentioned
23
+ - Set confidence based on how clear the extraction is (0.0 to 1.0)
24
+ - If the message is just a greeting or acknowledgment, return empty arrays
25
+ - **CRITICAL**: If a strategy is mentioned as successful or failed, mark the `outcome` field!
26
+
27
+ OUTPUT FORMAT (JSON):
28
+ {
29
+ "goals": [{"title": "...", "description": "...", "priority": 1-5, "status": "active", "parent_goal_title": null}],
30
+ "constraints": [{"type": "budget|stack|style|performance|time", "description": "...", "severity": "must|should|nice_to_have"}],
31
+ "preferences": [{"category": "coding_style|architecture|testing|tools|output_format", "preference": "...", "strength": "prefer|avoid|require"}],
32
+ "pain_points": [{"description": "...", "severity": "low|medium|high|critical", "related_goal": null}],
33
+ "strategies": [{"title": "...", "approach": "...", "rationale": "...", "outcome": "success|failure|pending", "outcome_reason": "...", "related_goal": null}],
34
+ "acceptance_criteria": [{"criterion": "...", "related_goal": "...", "testable": true}],
35
+ "code_references": [{"path": "...", "symbol": null, "start_line": null, "end_line": null, "action": "reference|create|modify|delete"}],
36
+ "next_actions": ["action 1", "action 2"],
37
+ "confidence": 0.85
38
+ }"""
39
+
40
+
41
+ def get_extractor_prompt(
42
+ user_text: str,
43
+ files: Optional[List[str]] = None,
44
+ diff: Optional[str] = None,
45
+ symbols: Optional[List[str]] = None,
46
+ context: Optional[str] = None,
47
+ ) -> Tuple[str, str]:
48
+ """
49
+ Build the extractor prompt for entity extraction.
50
+
51
+ Args:
52
+ user_text: The user's message
53
+ files: Optional list of files involved
54
+ diff: Optional code diff
55
+ symbols: Optional list of symbols
56
+ context: Optional additional context
57
+
58
+ Returns:
59
+ Tuple of (system_prompt, user_prompt)
60
+ """
61
+ user_prompt_parts = [f"USER MESSAGE:\n{user_text}"]
62
+
63
+ if files:
64
+ files_str = "\n".join(f"- {f}" for f in files)
65
+ user_prompt_parts.append(f"\nFILES INVOLVED:\n{files_str}")
66
+
67
+ if diff:
68
+ # Truncate large diffs
69
+ truncated_diff = diff[:2000] + "..." if len(diff) > 2000 else diff
70
+ user_prompt_parts.append(f"\nCODE DIFF:\n```\n{truncated_diff}\n```")
71
+
72
+ if symbols:
73
+ symbols_str = ", ".join(symbols)
74
+ user_prompt_parts.append(f"\nSYMBOLS REFERENCED: {symbols_str}")
75
+
76
+ if context:
77
+ user_prompt_parts.append(f"\nADDITIONAL CONTEXT:\n{context}")
78
+
79
+ user_prompt_parts.append(
80
+ "\n\nAnalyze the above and extract structured information. "
81
+ "Return a JSON object following the specified format."
82
+ )
83
+
84
+ return EXTRACTOR_SYSTEM_PROMPT, "\n".join(user_prompt_parts)
@@ -0,0 +1,117 @@
1
+ """
2
+ Linker prompt template for entity deduplication and relationship inference.
3
+ """
4
+
5
+ import json
6
+ from typing import Any, Dict, List, Tuple
7
+
8
+ from kg_mcp.llm.schemas import ExtractionResult
9
+
10
+
11
+ LINKER_SYSTEM_PROMPT = """You are an expert at analyzing knowledge graphs and detecting duplicates/relationships.
12
+
13
+ Your task is to:
14
+ 1. DETECT DUPLICATES: Check if any newly extracted entities are duplicates of existing ones
15
+ 2. SUGGEST MERGES: If entities are the same or very similar, suggest merging them
16
+ 3. INFER RELATIONSHIPS: Based on context, suggest relationships between entities
17
+
18
+ RELATIONSHIP TYPES:
19
+ - DECOMPOSES_INTO: A goal breaks down into subgoals
20
+ - HAS_CONSTRAINT: A goal has a constraint
21
+ - HAS_STRATEGY: A goal has a strategy for implementation
22
+ - BLOCKED_BY: An entity is blocked by a pain point
23
+ - IMPLEMENTED_BY: A goal is implemented by code artifacts
24
+ - VERIFIED_BY: A goal is verified by tests
25
+ - RELATED_TO: Generic relationship
26
+
27
+ RULES:
28
+ - Only suggest merges with high confidence (> 0.7)
29
+ - Consider semantic similarity, not just exact matches
30
+ - For relationships, provide clear reasoning
31
+ - Use existing entity IDs when available
32
+
33
+ OUTPUT FORMAT (JSON):
34
+ {
35
+ "merge_suggestions": [
36
+ {
37
+ "new_entity_type": "Goal|Preference|etc",
38
+ "new_entity_title": "...",
39
+ "existing_entity_id": "uuid",
40
+ "existing_entity_title": "...",
41
+ "confidence": 0.85,
42
+ "reason": "Why they should be merged"
43
+ }
44
+ ],
45
+ "relationships": [
46
+ {
47
+ "source_type": "Goal",
48
+ "source_id": "uuid or null",
49
+ "source_title": "...",
50
+ "relationship_type": "DECOMPOSES_INTO|HAS_CONSTRAINT|etc",
51
+ "target_type": "SubGoal",
52
+ "target_id": "uuid or null",
53
+ "target_title": "...",
54
+ "confidence": 0.8
55
+ }
56
+ ]
57
+ }"""
58
+
59
+
60
+ def get_linker_prompt(
61
+ extraction: ExtractionResult,
62
+ existing_goals: List[Dict[str, Any]],
63
+ existing_preferences: List[Dict[str, Any]],
64
+ recent_interactions: List[Dict[str, Any]],
65
+ ) -> Tuple[str, str]:
66
+ """
67
+ Build the linker prompt for entity linking and relationship inference.
68
+
69
+ Args:
70
+ extraction: The extraction result to link
71
+ existing_goals: List of existing goals from the graph
72
+ existing_preferences: List of existing preferences
73
+ recent_interactions: Recent interactions for context
74
+
75
+ Returns:
76
+ Tuple of (system_prompt, user_prompt)
77
+ """
78
+ user_prompt_parts = []
79
+
80
+ # Add extracted entities
81
+ user_prompt_parts.append("NEWLY EXTRACTED ENTITIES:")
82
+ user_prompt_parts.append(f"```json\n{extraction.model_dump_json(indent=2)}\n```")
83
+
84
+ # Add existing goals
85
+ if existing_goals:
86
+ user_prompt_parts.append("\nEXISTING GOALS IN GRAPH:")
87
+ for goal in existing_goals[:20]: # Limit to 20
88
+ user_prompt_parts.append(
89
+ f"- ID: {goal.get('id')}, Title: {goal.get('title')}, "
90
+ f"Status: {goal.get('status')}"
91
+ )
92
+
93
+ # Add existing preferences
94
+ if existing_preferences:
95
+ user_prompt_parts.append("\nEXISTING PREFERENCES:")
96
+ for pref in existing_preferences[:10]:
97
+ user_prompt_parts.append(
98
+ f"- ID: {pref.get('id')}, Category: {pref.get('category')}, "
99
+ f"Preference: {pref.get('preference')}"
100
+ )
101
+
102
+ # Add recent interaction context
103
+ if recent_interactions:
104
+ user_prompt_parts.append("\nRECENT INTERACTIONS (for context):")
105
+ for interaction in recent_interactions[:5]:
106
+ user_prompt_parts.append(
107
+ f"- {interaction.get('timestamp', 'N/A')}: "
108
+ f"{interaction.get('user_text', '')[:100]}..."
109
+ )
110
+
111
+ user_prompt_parts.append(
112
+ "\n\nAnalyze the newly extracted entities against the existing graph. "
113
+ "Suggest merges for duplicates and infer relationships. "
114
+ "Return a JSON object following the specified format."
115
+ )
116
+
117
+ return LINKER_SYSTEM_PROMPT, "\n".join(user_prompt_parts)
kg_mcp/llm/schemas.py ADDED
@@ -0,0 +1,248 @@
1
+ """
2
+ Pydantic schemas for LLM input/output validation.
3
+ These define the structured format for entity extraction and linking.
4
+ """
5
+
6
+ from datetime import datetime
7
+ from typing import List, Optional
8
+ from uuid import uuid4
9
+
10
+ from pydantic import BaseModel, Field
11
+
12
+
13
+ # =============================================================================
14
+ # Extraction Schemas
15
+ # =============================================================================
16
+
17
+
18
+ class GoalExtract(BaseModel):
19
+ """Extracted goal from user text."""
20
+
21
+ title: str = Field(..., description="Short title of the goal")
22
+ description: Optional[str] = Field(None, description="Detailed description")
23
+ priority: int = Field(default=2, ge=1, le=5, description="Priority 1-5 (1=highest)")
24
+ status: str = Field(default="active", description="Status: active, paused, done")
25
+ parent_goal_title: Optional[str] = Field(
26
+ None, description="Title of parent goal if this is a subgoal"
27
+ )
28
+
29
+
30
+ class ConstraintExtract(BaseModel):
31
+ """Extracted constraint from user text."""
32
+
33
+ type: str = Field(..., description="Constraint type: budget, stack, style, performance, time")
34
+ description: str = Field(..., description="Description of the constraint")
35
+ severity: str = Field(default="must", description="Severity: must, should, nice_to_have")
36
+
37
+
38
+ class PreferenceExtract(BaseModel):
39
+ """Extracted preference from user text."""
40
+
41
+ category: str = Field(
42
+ ...,
43
+ description="Category: coding_style, architecture, testing, tools, output_format",
44
+ )
45
+ preference: str = Field(..., description="The preference itself")
46
+ strength: str = Field(default="prefer", description="Strength: prefer, avoid, require")
47
+
48
+
49
+ class PainPointExtract(BaseModel):
50
+ """Extracted pain point from user text."""
51
+
52
+ description: str = Field(..., description="Description of the pain point")
53
+ severity: str = Field(default="medium", description="Severity: low, medium, high, critical")
54
+ related_goal: Optional[str] = Field(None, description="Related goal title if any")
55
+
56
+
57
+ class StrategyExtract(BaseModel):
58
+ """Extracted strategy from user text."""
59
+
60
+ title: str = Field(..., description="Short title of the strategy")
61
+ approach: str = Field(..., description="Description of the approach")
62
+ rationale: Optional[str] = Field(None, description="Why this strategy was chosen")
63
+ outcome: Optional[str] = Field(None, description="Outcome of the strategy: success, failure, pending")
64
+ outcome_reason: Optional[str] = Field(None, description="Reason for the outcome")
65
+ related_goal: Optional[str] = Field(None, description="Related goal title if any")
66
+
67
+
68
+ class AcceptanceCriteriaExtract(BaseModel):
69
+ """Extracted acceptance criteria from user text."""
70
+
71
+ criterion: str = Field(..., description="The acceptance criterion")
72
+ related_goal: Optional[str] = Field(None, description="Related goal title")
73
+ testable: bool = Field(default=True, description="Whether it's testable")
74
+
75
+
76
+ class CodeReference(BaseModel):
77
+ """Reference to code in the message."""
78
+
79
+ path: str = Field(..., description="File path")
80
+ symbol: Optional[str] = Field(None, description="Symbol name (function/class)")
81
+ start_line: Optional[int] = Field(None, description="Start line number")
82
+ end_line: Optional[int] = Field(None, description="End line number")
83
+ action: str = Field(
84
+ default="reference", description="Action: reference, create, modify, delete"
85
+ )
86
+
87
+
88
+ class ExtractionResult(BaseModel):
89
+ """Complete result of entity extraction from user text."""
90
+
91
+ goals: List[GoalExtract] = Field(default_factory=list)
92
+ constraints: List[ConstraintExtract] = Field(default_factory=list)
93
+ preferences: List[PreferenceExtract] = Field(default_factory=list)
94
+ pain_points: List[PainPointExtract] = Field(default_factory=list)
95
+ strategies: List[StrategyExtract] = Field(default_factory=list)
96
+ acceptance_criteria: List[AcceptanceCriteriaExtract] = Field(default_factory=list)
97
+ code_references: List[CodeReference] = Field(default_factory=list)
98
+ next_actions: List[str] = Field(default_factory=list)
99
+ confidence: float = Field(default=0.8, ge=0.0, le=1.0)
100
+
101
+
102
+ # =============================================================================
103
+ # Linking Schemas
104
+ # =============================================================================
105
+
106
+
107
+ class MergeSuggestion(BaseModel):
108
+ """Suggestion to merge a new entity with an existing one."""
109
+
110
+ new_entity_type: str = Field(..., description="Type of the new entity")
111
+ new_entity_title: str = Field(..., description="Title of the new entity")
112
+ existing_entity_id: str = Field(..., description="ID of the existing entity to merge with")
113
+ existing_entity_title: str = Field(..., description="Title of existing entity")
114
+ confidence: float = Field(default=0.8, ge=0.0, le=1.0)
115
+ reason: str = Field(..., description="Why these should be merged")
116
+
117
+
118
+ class RelationshipSuggestion(BaseModel):
119
+ """Suggestion for a new relationship between entities."""
120
+
121
+ source_type: str = Field(..., description="Type of source entity")
122
+ source_id: Optional[str] = Field(None, description="ID of source entity (if existing)")
123
+ source_title: str = Field(..., description="Title of source entity")
124
+ relationship_type: str = Field(..., description="Type of relationship (e.g., IMPLEMENTED_BY)")
125
+ target_type: str = Field(..., description="Type of target entity")
126
+ target_id: Optional[str] = Field(None, description="ID of target entity (if existing)")
127
+ target_title: str = Field(..., description="Title of target entity")
128
+ confidence: float = Field(default=0.8, ge=0.0, le=1.0)
129
+
130
+
131
+ class LinkingResult(BaseModel):
132
+ """Result of entity linking analysis."""
133
+
134
+ merge_suggestions: List[MergeSuggestion] = Field(default_factory=list)
135
+ relationships: List[RelationshipSuggestion] = Field(default_factory=list)
136
+
137
+
138
+ # =============================================================================
139
+ # Graph Node Schemas (for Neo4j)
140
+ # =============================================================================
141
+
142
+
143
+ class BaseNode(BaseModel):
144
+ """Base class for all graph nodes."""
145
+
146
+ id: str = Field(default_factory=lambda: str(uuid4()))
147
+ created_at: datetime = Field(default_factory=datetime.utcnow)
148
+ updated_at: datetime = Field(default_factory=datetime.utcnow)
149
+
150
+
151
+ class InteractionNode(BaseNode):
152
+ """Represents a user interaction/request."""
153
+
154
+ user_text: str
155
+ assistant_text: Optional[str] = None
156
+ tags: List[str] = Field(default_factory=list)
157
+ project_id: str
158
+
159
+
160
+ class GoalNode(BaseNode):
161
+ """Represents a goal or objective."""
162
+
163
+ title: str
164
+ description: Optional[str] = None
165
+ status: str = "active"
166
+ priority: int = 2
167
+ project_id: str
168
+
169
+
170
+ class ConstraintNode(BaseNode):
171
+ """Represents a constraint."""
172
+
173
+ type: str
174
+ description: str
175
+ severity: str = "must"
176
+ project_id: str
177
+
178
+
179
+ class PreferenceNode(BaseNode):
180
+ """Represents a user preference."""
181
+
182
+ category: str
183
+ preference: str
184
+ strength: str = "prefer"
185
+ user_id: str
186
+
187
+
188
+ class PainPointNode(BaseNode):
189
+ """Represents a pain point."""
190
+
191
+ description: str
192
+ severity: str = "medium"
193
+ resolved: bool = False
194
+ project_id: str
195
+
196
+
197
+ class StrategyNode(BaseNode):
198
+ """Represents a strategy or approach."""
199
+
200
+ title: str
201
+ approach: str
202
+ rationale: Optional[str] = None
203
+ outcome: Optional[str] = None
204
+ outcome_reason: Optional[str] = None
205
+ project_id: str
206
+
207
+
208
+ class DecisionNode(BaseNode):
209
+ """Represents an ADR-lite decision."""
210
+
211
+ title: str
212
+ decision: str
213
+ rationale: str
214
+ alternatives: List[str] = Field(default_factory=list)
215
+ project_id: str
216
+
217
+
218
+ class CodeArtifactNode(BaseNode):
219
+ """Represents a code artifact (file, function, class, snippet)."""
220
+
221
+ path: str
222
+ language: Optional[str] = None
223
+ kind: str = "file" # file, function, class, snippet
224
+ git_commit: Optional[str] = None
225
+ content_hash: Optional[str] = None
226
+ start_line: Optional[int] = None
227
+ end_line: Optional[int] = None
228
+ project_id: str
229
+
230
+
231
+ class SymbolNode(BaseNode):
232
+ """Represents a code symbol (function, class, method)."""
233
+
234
+ fqn: str # fully qualified name
235
+ name: str
236
+ kind: str # function, class, method, variable
237
+ signature: Optional[str] = None
238
+ artifact_id: str
239
+
240
+
241
+ class TestCaseNode(BaseNode):
242
+ """Represents a test case."""
243
+
244
+ name: str
245
+ kind: str = "unit" # unit, integration, e2e, manual
246
+ path: Optional[str] = None
247
+ status: str = "pending" # pending, passed, failed
248
+ project_id: str
kg_mcp/main.py ADDED
@@ -0,0 +1,195 @@
1
+ """
2
+ Main entry point for the MCP-KG-Memory server.
3
+ Supports both STDIO and Streamable HTTP transports for Antigravity compatibility.
4
+
5
+ Usage:
6
+ # STDIO mode (for Antigravity command/args config)
7
+ python -m kg_mcp --transport stdio
8
+
9
+ # HTTP mode (for Antigravity serverUrl config or standalone)
10
+ python -m kg_mcp --transport http --host 127.0.0.1 --port 8000
11
+ """
12
+
13
+ import argparse
14
+ import logging
15
+ import os
16
+ import signal
17
+ import sys
18
+ from typing import Optional
19
+
20
+ from mcp.server.fastmcp import FastMCP
21
+
22
+ from kg_mcp.config import get_settings
23
+ from kg_mcp.mcp.tools import register_tools
24
+ from kg_mcp.mcp.resources import register_resources
25
+ from kg_mcp.mcp.prompts import register_prompts
26
+
27
+
28
+ def setup_logging(transport: str) -> logging.Logger:
29
+ """
30
+ Configure logging based on transport mode.
31
+
32
+ For STDIO: Log ONLY to stderr (stdout is reserved for MCP protocol)
33
+ For HTTP: Log to stdout
34
+ """
35
+ settings = get_settings()
36
+ log_level = getattr(logging, settings.log_level.upper(), logging.INFO)
37
+
38
+ # Clear any existing handlers
39
+ root_logger = logging.getLogger()
40
+ root_logger.handlers.clear()
41
+
42
+ if transport == "stdio":
43
+ # CRITICAL: In stdio mode, stdout is for MCP protocol ONLY
44
+ # All logs must go to stderr
45
+ handler = logging.StreamHandler(sys.stderr)
46
+ else:
47
+ handler = logging.StreamHandler(sys.stdout)
48
+
49
+ handler.setFormatter(
50
+ logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
51
+ )
52
+ root_logger.addHandler(handler)
53
+ root_logger.setLevel(log_level)
54
+
55
+ return logging.getLogger(__name__)
56
+
57
+
58
+ def create_mcp_server(json_response: bool = True, stateless: bool = True) -> FastMCP:
59
+ """Create and configure the MCP server instance."""
60
+ logger = logging.getLogger(__name__)
61
+ logger.info("Initializing MCP-KG-Memory Server...")
62
+
63
+ # Create FastMCP instance
64
+ mcp = FastMCP(
65
+ "KG Memory Server",
66
+ json_response=json_response,
67
+ stateless_http=stateless,
68
+ )
69
+
70
+ # Register all MCP components
71
+ register_tools(mcp)
72
+ register_resources(mcp)
73
+ register_prompts(mcp)
74
+
75
+ logger.info("MCP server components registered successfully")
76
+ return mcp
77
+
78
+
79
+ def handle_shutdown(signum, frame):
80
+ """Handle graceful shutdown on SIGTERM/SIGINT."""
81
+ logger = logging.getLogger(__name__)
82
+ logger.info(f"Received signal {signum}, shutting down gracefully...")
83
+ sys.exit(0)
84
+
85
+
86
+ def run_stdio():
87
+ """Run server in STDIO mode (for Antigravity command/args config)."""
88
+ logger = setup_logging("stdio")
89
+ logger.info("Starting MCP-KG-Memory Server in STDIO mode")
90
+
91
+ # Register signal handlers for graceful shutdown
92
+ signal.signal(signal.SIGTERM, handle_shutdown)
93
+ signal.signal(signal.SIGINT, handle_shutdown)
94
+
95
+ settings = get_settings()
96
+ if settings.kg_mcp_token:
97
+ logger.info("Token authentication configured")
98
+ else:
99
+ logger.warning("No authentication token configured")
100
+
101
+ logger.info(f"LLM Model: {settings.llm_model}")
102
+ logger.info(f"Neo4j URI: {settings.neo4j_uri}")
103
+
104
+ # Create and run server
105
+ mcp = create_mcp_server(json_response=True, stateless=True)
106
+
107
+ # Run with stdio transport
108
+ mcp.run(transport="stdio")
109
+
110
+
111
+ def run_http(host: str = "127.0.0.1", port: int = 8000, path: str = "/mcp"):
112
+ """Run server in HTTP mode (for Antigravity serverUrl config or standalone)."""
113
+ logger = setup_logging("http")
114
+ logger.info(f"Starting MCP-KG-Memory Server in HTTP mode on {host}:{port}{path}")
115
+
116
+ # Register signal handlers
117
+ signal.signal(signal.SIGTERM, handle_shutdown)
118
+ signal.signal(signal.SIGINT, handle_shutdown)
119
+
120
+ settings = get_settings()
121
+ if settings.kg_mcp_token:
122
+ logger.info("Bearer token authentication enabled")
123
+ else:
124
+ logger.warning("⚠️ No authentication token configured! Set KG_MCP_TOKEN in .env")
125
+
126
+ logger.info(f"LLM Model: {settings.llm_model}")
127
+ logger.info(f"Neo4j URI: {settings.neo4j_uri}")
128
+
129
+ # Create and run server
130
+ mcp = create_mcp_server(json_response=True, stateless=True)
131
+
132
+ # Run with streamable-http transport
133
+ mcp.run(
134
+ transport="streamable-http",
135
+ host=host,
136
+ port=port,
137
+ )
138
+
139
+
140
+ def main():
141
+ """Main entry point with CLI argument parsing."""
142
+ parser = argparse.ArgumentParser(
143
+ description="MCP-KG-Memory Server - Knowledge Graph Memory for IDE Agents",
144
+ formatter_class=argparse.RawDescriptionHelpFormatter,
145
+ epilog="""
146
+ Examples:
147
+ # STDIO mode (for Antigravity command config)
148
+ python -m kg_mcp --transport stdio
149
+
150
+ # HTTP mode (for Antigravity serverUrl config)
151
+ python -m kg_mcp --transport http --host 127.0.0.1 --port 8000
152
+
153
+ # Using console script
154
+ kg-mcp --transport stdio
155
+ """,
156
+ )
157
+
158
+ parser.add_argument(
159
+ "--transport",
160
+ "-t",
161
+ choices=["stdio", "http"],
162
+ default="http",
163
+ help="Transport mode: 'stdio' for command-based, 'http' for serverUrl-based (default: http)",
164
+ )
165
+
166
+ parser.add_argument(
167
+ "--host",
168
+ default="127.0.0.1",
169
+ help="Host to bind to in HTTP mode (default: 127.0.0.1)",
170
+ )
171
+
172
+ parser.add_argument(
173
+ "--port",
174
+ "-p",
175
+ type=int,
176
+ default=8000,
177
+ help="Port to listen on in HTTP mode (default: 8000)",
178
+ )
179
+
180
+ parser.add_argument(
181
+ "--path",
182
+ default="/mcp",
183
+ help="MCP endpoint path in HTTP mode (default: /mcp)",
184
+ )
185
+
186
+ args = parser.parse_args()
187
+
188
+ if args.transport == "stdio":
189
+ run_stdio()
190
+ else:
191
+ run_http(host=args.host, port=args.port, path=args.path)
192
+
193
+
194
+ if __name__ == "__main__":
195
+ main()
kg_mcp/mcp/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ """
2
+ MCP submodule for tools, resources, and prompts.
3
+ """