traia-iatp 0.1.1__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.
Potentially problematic release.
This version of traia-iatp might be problematic. Click here for more details.
- traia_iatp/README.md +368 -0
- traia_iatp/__init__.py +30 -0
- traia_iatp/cli/__init__.py +5 -0
- traia_iatp/cli/main.py +483 -0
- traia_iatp/client/__init__.py +10 -0
- traia_iatp/client/a2a_client.py +274 -0
- traia_iatp/client/crewai_a2a_tools.py +335 -0
- traia_iatp/client/grpc_a2a_tools.py +349 -0
- traia_iatp/client/root_path_a2a_client.py +1 -0
- traia_iatp/core/__init__.py +43 -0
- traia_iatp/core/models.py +161 -0
- traia_iatp/mcp/__init__.py +15 -0
- traia_iatp/mcp/client.py +201 -0
- traia_iatp/mcp/mcp_agent_template.py +422 -0
- traia_iatp/mcp/templates/Dockerfile.j2 +56 -0
- traia_iatp/mcp/templates/README.md.j2 +212 -0
- traia_iatp/mcp/templates/cursor-rules.md.j2 +326 -0
- traia_iatp/mcp/templates/deployment_params.json.j2 +20 -0
- traia_iatp/mcp/templates/docker-compose.yml.j2 +23 -0
- traia_iatp/mcp/templates/dockerignore.j2 +47 -0
- traia_iatp/mcp/templates/gitignore.j2 +77 -0
- traia_iatp/mcp/templates/mcp_health_check.py.j2 +150 -0
- traia_iatp/mcp/templates/pyproject.toml.j2 +26 -0
- traia_iatp/mcp/templates/run_local_docker.sh.j2 +94 -0
- traia_iatp/mcp/templates/server.py.j2 +240 -0
- traia_iatp/mcp/traia_mcp_adapter.py +381 -0
- traia_iatp/preview_diagrams.html +181 -0
- traia_iatp/registry/__init__.py +26 -0
- traia_iatp/registry/atlas_search_indexes.json +280 -0
- traia_iatp/registry/embeddings.py +298 -0
- traia_iatp/registry/iatp_search_api.py +839 -0
- traia_iatp/registry/mongodb_registry.py +771 -0
- traia_iatp/registry/readmes/ATLAS_SEARCH_INDEXES.md +252 -0
- traia_iatp/registry/readmes/ATLAS_SEARCH_SETUP.md +134 -0
- traia_iatp/registry/readmes/AUTHENTICATION_UPDATE.md +124 -0
- traia_iatp/registry/readmes/EMBEDDINGS_SETUP.md +172 -0
- traia_iatp/registry/readmes/IATP_SEARCH_API_GUIDE.md +257 -0
- traia_iatp/registry/readmes/MONGODB_X509_AUTH.md +208 -0
- traia_iatp/registry/readmes/README.md +251 -0
- traia_iatp/registry/readmes/REFACTORING_SUMMARY.md +191 -0
- traia_iatp/server/__init__.py +15 -0
- traia_iatp/server/a2a_server.py +215 -0
- traia_iatp/server/example_template_usage.py +72 -0
- traia_iatp/server/iatp_server_agent_generator.py +237 -0
- traia_iatp/server/iatp_server_template_generator.py +235 -0
- traia_iatp/server/templates/Dockerfile.j2 +49 -0
- traia_iatp/server/templates/README.md +137 -0
- traia_iatp/server/templates/README.md.j2 +425 -0
- traia_iatp/server/templates/__init__.py +1 -0
- traia_iatp/server/templates/__main__.py.j2 +450 -0
- traia_iatp/server/templates/agent.py.j2 +80 -0
- traia_iatp/server/templates/agent_config.json.j2 +22 -0
- traia_iatp/server/templates/agent_executor.py.j2 +264 -0
- traia_iatp/server/templates/docker-compose.yml.j2 +23 -0
- traia_iatp/server/templates/env.example.j2 +67 -0
- traia_iatp/server/templates/gitignore.j2 +78 -0
- traia_iatp/server/templates/grpc_server.py.j2 +218 -0
- traia_iatp/server/templates/pyproject.toml.j2 +76 -0
- traia_iatp/server/templates/run_local_docker.sh.j2 +103 -0
- traia_iatp/server/templates/server.py.j2 +190 -0
- traia_iatp/special_agencies/__init__.py +4 -0
- traia_iatp/special_agencies/registry_search_agency.py +392 -0
- traia_iatp/utils/__init__.py +10 -0
- traia_iatp/utils/docker_utils.py +251 -0
- traia_iatp/utils/general.py +64 -0
- traia_iatp/utils/iatp_utils.py +126 -0
- traia_iatp-0.1.1.dist-info/METADATA +414 -0
- traia_iatp-0.1.1.dist-info/RECORD +72 -0
- traia_iatp-0.1.1.dist-info/WHEEL +5 -0
- traia_iatp-0.1.1.dist-info/entry_points.txt +2 -0
- traia_iatp-0.1.1.dist-info/licenses/LICENSE +21 -0
- traia_iatp-0.1.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
"""
|
|
3
|
+
{{ api_name }} MCP Server Health Check Script
|
|
4
|
+
|
|
5
|
+
This script properly connects to the {{ api_name }} MCP server and checks its health by:
|
|
6
|
+
1. Establishing a session
|
|
7
|
+
2. Requesting server info
|
|
8
|
+
3. Listing available tools
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import sys
|
|
12
|
+
import json
|
|
13
|
+
import requests
|
|
14
|
+
import argparse
|
|
15
|
+
from typing import Dict, Any
|
|
16
|
+
import uuid
|
|
17
|
+
|
|
18
|
+
def create_mcp_session(base_url: str) -> Dict[str, Any]:
|
|
19
|
+
"""Create an MCP session and return session info"""
|
|
20
|
+
# Generate a session ID
|
|
21
|
+
session_id = str(uuid.uuid4())
|
|
22
|
+
|
|
23
|
+
# MCP requires specific headers for streamable-http
|
|
24
|
+
headers = {
|
|
25
|
+
"Content-Type": "application/json",
|
|
26
|
+
"Accept": "application/json, text/event-stream",
|
|
27
|
+
"X-Session-ID": session_id
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return {"session_id": session_id, "headers": headers, "base_url": base_url}
|
|
31
|
+
|
|
32
|
+
def send_mcp_request(session: Dict[str, Any], method: str, params: Dict = None) -> Dict[str, Any]:
|
|
33
|
+
"""Send an MCP JSON-RPC request"""
|
|
34
|
+
request_data = {
|
|
35
|
+
"jsonrpc": "2.0",
|
|
36
|
+
"method": method,
|
|
37
|
+
"params": params or {},
|
|
38
|
+
"id": str(uuid.uuid4())
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
try:
|
|
42
|
+
response = requests.post(
|
|
43
|
+
f"{session['base_url']}/mcp/",
|
|
44
|
+
json=request_data,
|
|
45
|
+
headers=session['headers'],
|
|
46
|
+
timeout=5
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
# Handle both JSON and SSE responses
|
|
50
|
+
if response.headers.get('content-type', '').startswith('text/event-stream'):
|
|
51
|
+
# For SSE, we'd need to parse the event stream
|
|
52
|
+
return {"status": "ok", "message": "Server returned SSE stream"}
|
|
53
|
+
else:
|
|
54
|
+
return response.json()
|
|
55
|
+
|
|
56
|
+
except requests.exceptions.RequestException as e:
|
|
57
|
+
return {"error": str(e)}
|
|
58
|
+
|
|
59
|
+
def check_mcp_server_health(url: str) -> bool:
|
|
60
|
+
"""Check if {{ api_name }} MCP server is healthy"""
|
|
61
|
+
print(f"🔍 Checking {{ api_name }} MCP server health at {url}")
|
|
62
|
+
|
|
63
|
+
# Create session
|
|
64
|
+
session = create_mcp_session(url)
|
|
65
|
+
print(f"📝 Created session: {session['session_id']}")
|
|
66
|
+
|
|
67
|
+
# Try to get server info
|
|
68
|
+
print("\n1️⃣ Testing server.info method...")
|
|
69
|
+
result = send_mcp_request(session, "server.info")
|
|
70
|
+
|
|
71
|
+
if "error" in result and "session" not in str(result.get("error", "")):
|
|
72
|
+
print(f"❌ Server info failed: {result}")
|
|
73
|
+
return False
|
|
74
|
+
else:
|
|
75
|
+
print(f"✅ Server responded: {json.dumps(result, indent=2)[:200]}...")
|
|
76
|
+
|
|
77
|
+
# Try to list tools
|
|
78
|
+
print("\n2️⃣ Testing tools/list method...")
|
|
79
|
+
result = send_mcp_request(session, "tools/list")
|
|
80
|
+
|
|
81
|
+
if "error" in result and "session" not in str(result.get("error", "")):
|
|
82
|
+
print(f"❌ List tools failed: {result}")
|
|
83
|
+
return False
|
|
84
|
+
else:
|
|
85
|
+
print(f"✅ Tools list responded: {json.dumps(result, indent=2)[:200]}...")
|
|
86
|
+
|
|
87
|
+
# Check if we have the expected tools
|
|
88
|
+
if "result" in result and "tools" in result["result"]:
|
|
89
|
+
tools = result["result"]["tools"]
|
|
90
|
+
tool_names = [tool.get("name", "") for tool in tools]
|
|
91
|
+
print(f"📋 Available tools: {', '.join(tool_names)}")
|
|
92
|
+
|
|
93
|
+
# Check for expected tools
|
|
94
|
+
expected_tools = ["example_tool", "get_api_info"]
|
|
95
|
+
missing_tools = [tool for tool in expected_tools if tool not in tool_names]
|
|
96
|
+
|
|
97
|
+
if missing_tools:
|
|
98
|
+
print(f"⚠️ Missing expected tools: {', '.join(missing_tools)}")
|
|
99
|
+
else:
|
|
100
|
+
print("✅ All expected {{ api_name }} tools are available!")
|
|
101
|
+
|
|
102
|
+
# Alternative: Try connecting with CrewAI adapter
|
|
103
|
+
print("\n3️⃣ Testing CrewAI adapter connection...")
|
|
104
|
+
try:
|
|
105
|
+
from crewai_tools import MCPServerAdapter
|
|
106
|
+
|
|
107
|
+
server_params = {
|
|
108
|
+
"url": f"{url}/mcp/",
|
|
109
|
+
"transport": "streamable-http"
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
with MCPServerAdapter(server_params) as mcp_tools:
|
|
113
|
+
tools = list(mcp_tools)
|
|
114
|
+
print(f"✅ CrewAI connected successfully! Found {len(tools)} tools")
|
|
115
|
+
|
|
116
|
+
# Print first few tools
|
|
117
|
+
for i, tool in enumerate(tools[:3]):
|
|
118
|
+
print(f" - {tool.name}")
|
|
119
|
+
|
|
120
|
+
if len(tools) > 3:
|
|
121
|
+
print(f" ... and {len(tools) - 3} more")
|
|
122
|
+
|
|
123
|
+
return True
|
|
124
|
+
|
|
125
|
+
except Exception as e:
|
|
126
|
+
print(f"⚠️ CrewAI adapter test failed: {e}")
|
|
127
|
+
# This might fail but server could still be healthy
|
|
128
|
+
|
|
129
|
+
return True
|
|
130
|
+
|
|
131
|
+
def main():
|
|
132
|
+
parser = argparse.ArgumentParser(description="Check {{ api_name }} MCP Server Health")
|
|
133
|
+
parser.add_argument("--url", default="http://localhost:8000",
|
|
134
|
+
help="{{ api_name }} MCP server URL (default: http://localhost:8000)")
|
|
135
|
+
args = parser.parse_args()
|
|
136
|
+
|
|
137
|
+
print(f"🚀 {{ api_name }} MCP Server Health Check")
|
|
138
|
+
print(f"📍 Server URL: {args.url}")
|
|
139
|
+
print(f"📰 Expected tools: example_tool, get_api_info")
|
|
140
|
+
print("="*50)
|
|
141
|
+
|
|
142
|
+
if check_mcp_server_health(args.url):
|
|
143
|
+
print("\n✅ {{ api_name }} MCP Server is healthy and responding!")
|
|
144
|
+
return 0
|
|
145
|
+
else:
|
|
146
|
+
print("\n❌ {{ api_name }} MCP Server health check failed!")
|
|
147
|
+
return 1
|
|
148
|
+
|
|
149
|
+
if __name__ == "__main__":
|
|
150
|
+
sys.exit(main())
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "{{ api_slug }}-mcp-server"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "MCP server for {{ api_name }} API{% if auth_description %} with authentication support{% endif %}"
|
|
5
|
+
requires-python = ">=3.12"
|
|
6
|
+
dependencies = [
|
|
7
|
+
"crewai-tools[mcp]>=0.48.0",
|
|
8
|
+
"fastmcp>=2.10.1",
|
|
9
|
+
"python-dotenv>=1.1.1",
|
|
10
|
+
"requests>=2.32.4",
|
|
11
|
+
"starlette>=0.46.2",
|
|
12
|
+
"retry>=0.9.2",
|
|
13
|
+
{% if sdk_package %}
|
|
14
|
+
"{{ sdk_package }}",
|
|
15
|
+
{% endif %}
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
[build-system]
|
|
19
|
+
requires = ["hatchling"]
|
|
20
|
+
build-backend = "hatchling.build"
|
|
21
|
+
|
|
22
|
+
[tool.hatch.build.targets.wheel]
|
|
23
|
+
include = [
|
|
24
|
+
"server.py",
|
|
25
|
+
"mcp_health_check.py",
|
|
26
|
+
]
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# Script to build and run the {{ api_name }} MCP Server locally in Docker
|
|
4
|
+
|
|
5
|
+
set -e # Exit on error
|
|
6
|
+
|
|
7
|
+
# Color codes for output
|
|
8
|
+
GREEN='\033[0;32m'
|
|
9
|
+
BLUE='\033[0;34m'
|
|
10
|
+
YELLOW='\033[1;33m'
|
|
11
|
+
RED='\033[0;31m'
|
|
12
|
+
NC='\033[0m' # No Color
|
|
13
|
+
|
|
14
|
+
# Configuration
|
|
15
|
+
IMAGE_NAME="{{ api_slug }}-mcp-server"
|
|
16
|
+
CONTAINER_NAME="{{ api_slug }}-mcp-local"
|
|
17
|
+
HOST_PORT=8000
|
|
18
|
+
CONTAINER_PORT=8000
|
|
19
|
+
|
|
20
|
+
echo -e "${BLUE}🚀 Building and running {{ api_name }} MCP Server...${NC}"
|
|
21
|
+
echo
|
|
22
|
+
|
|
23
|
+
# Check if Docker is installed
|
|
24
|
+
if ! command -v docker &> /dev/null; then
|
|
25
|
+
echo -e "${RED}❌ Docker is not installed. Please install Docker first.${NC}"
|
|
26
|
+
exit 1
|
|
27
|
+
fi
|
|
28
|
+
|
|
29
|
+
# Stop and remove existing container if it exists
|
|
30
|
+
if docker ps -a | grep -q $CONTAINER_NAME; then
|
|
31
|
+
echo -e "${YELLOW}🛑 Stopping existing container...${NC}"
|
|
32
|
+
docker stop $CONTAINER_NAME >/dev/null 2>&1 || true
|
|
33
|
+
docker rm $CONTAINER_NAME >/dev/null 2>&1 || true
|
|
34
|
+
fi
|
|
35
|
+
|
|
36
|
+
# Build the Docker image
|
|
37
|
+
echo -e "${BLUE}🔨 Building Docker image...${NC}"
|
|
38
|
+
docker build --no-cache -t $IMAGE_NAME .
|
|
39
|
+
|
|
40
|
+
# Run the container
|
|
41
|
+
echo -e "${BLUE}🏃 Starting container...${NC}"
|
|
42
|
+
docker run -d \
|
|
43
|
+
--name $CONTAINER_NAME \
|
|
44
|
+
-p $HOST_PORT:$CONTAINER_PORT \
|
|
45
|
+
-e STAGE="${STAGE:-MAINNET}" \
|
|
46
|
+
-e LOG_LEVEL="${LOG_LEVEL:-INFO}" \
|
|
47
|
+
{% if api_key_env_var %}
|
|
48
|
+
-e {{ api_key_env_var }}="{% raw %}${{% endraw %}{{ api_key_env_var }}{% raw %}}{% endraw %}" \
|
|
49
|
+
{% endif %}
|
|
50
|
+
-e PORT="$CONTAINER_PORT" \
|
|
51
|
+
$IMAGE_NAME
|
|
52
|
+
|
|
53
|
+
# Wait for the server to start
|
|
54
|
+
echo -e "${YELLOW}⏳ Waiting for server to start...${NC}"
|
|
55
|
+
sleep 3
|
|
56
|
+
|
|
57
|
+
# Check if container is running
|
|
58
|
+
if ! docker ps | grep -q $CONTAINER_NAME; then
|
|
59
|
+
echo -e "${RED}❌ Container failed to start. Checking logs:${NC}"
|
|
60
|
+
docker logs $CONTAINER_NAME
|
|
61
|
+
exit 1
|
|
62
|
+
fi
|
|
63
|
+
|
|
64
|
+
# Get container info
|
|
65
|
+
CONTAINER_ID=$(docker ps -q -f name=$CONTAINER_NAME)
|
|
66
|
+
CONTAINER_IP=$(docker inspect -f '{% raw %}{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}{% endraw %}' $CONTAINER_ID)
|
|
67
|
+
|
|
68
|
+
# Output connection information
|
|
69
|
+
echo
|
|
70
|
+
echo -e "${GREEN}✅ {{ api_name }} MCP Server is running!${NC}"
|
|
71
|
+
echo
|
|
72
|
+
echo -e "${BLUE}📍 Connection Information:${NC}"
|
|
73
|
+
echo -e " Local URL: ${GREEN}http://localhost:${HOST_PORT}/mcp${NC}"
|
|
74
|
+
echo -e " Container IP: ${GREEN}${CONTAINER_IP}:${CONTAINER_PORT}${NC}"
|
|
75
|
+
echo -e " Container Name: ${GREEN}${CONTAINER_NAME}${NC}"
|
|
76
|
+
echo -e " Container ID: ${GREEN}${CONTAINER_ID:0:12}${NC}"
|
|
77
|
+
echo
|
|
78
|
+
echo -e "${BLUE}📝 Useful commands:${NC}"
|
|
79
|
+
echo -e " View logs: ${YELLOW}docker logs -f ${CONTAINER_NAME}${NC}"
|
|
80
|
+
echo -e " Stop server: ${YELLOW}docker stop ${CONTAINER_NAME}${NC}"
|
|
81
|
+
echo -e " Remove container: ${YELLOW}docker rm ${CONTAINER_NAME}${NC}"
|
|
82
|
+
echo -e " Shell access: ${YELLOW}docker exec -it ${CONTAINER_NAME} /bin/bash${NC}"
|
|
83
|
+
echo
|
|
84
|
+
echo -e "${BLUE}🔌 MCP Server Endpoint:${NC}"
|
|
85
|
+
echo -e " ${GREEN}http://localhost:${HOST_PORT}/mcp${NC}"
|
|
86
|
+
echo
|
|
87
|
+
|
|
88
|
+
# Check if the server is responding
|
|
89
|
+
echo -e "${YELLOW}🔍 Checking server health...${NC}"
|
|
90
|
+
if curl -s -o /dev/null -w "%{http_code}" "http://localhost:${HOST_PORT}/mcp" | grep -q "200\|404\|405"; then
|
|
91
|
+
echo -e "${GREEN}✅ Server is responding!${NC}"
|
|
92
|
+
else
|
|
93
|
+
echo -e "${YELLOW}⚠️ Server may still be starting up. Check logs with: docker logs -f ${CONTAINER_NAME}${NC}"
|
|
94
|
+
fi
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
{{ api_name }} MCP Server{{ auth_description }}
|
|
4
|
+
|
|
5
|
+
A Model Context Protocol server providing {{ api_description }}
|
|
6
|
+
using the {{ api_name }} API{{ auth_details }}.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import asyncio
|
|
10
|
+
import logging
|
|
11
|
+
import os
|
|
12
|
+
import sys
|
|
13
|
+
from typing import Dict, Any, Optional
|
|
14
|
+
from datetime import datetime
|
|
15
|
+
|
|
16
|
+
# Third-party imports
|
|
17
|
+
import requests
|
|
18
|
+
from fastmcp import FastMCP, Context
|
|
19
|
+
from fastmcp.server.middleware import Middleware, MiddlewareContext
|
|
20
|
+
from fastmcp.server.dependencies import get_http_request, get_context
|
|
21
|
+
from starlette.requests import Request
|
|
22
|
+
from starlette.responses import JSONResponse
|
|
23
|
+
from retry import retry
|
|
24
|
+
{% if sdk_package %}
|
|
25
|
+
# {{ api_name }} SDK
|
|
26
|
+
# TODO: Adjust the import based on the SDK documentation
|
|
27
|
+
# Common patterns:
|
|
28
|
+
# - from {{ sdk_module }} import Client
|
|
29
|
+
# - from {{ sdk_module }} import {{ api_name }}Client
|
|
30
|
+
# - import {{ sdk_module }}
|
|
31
|
+
# Check the SDK docs for the correct import statement
|
|
32
|
+
import {{ sdk_module }}
|
|
33
|
+
{% endif %}
|
|
34
|
+
|
|
35
|
+
# Configure logging
|
|
36
|
+
logging.basicConfig(
|
|
37
|
+
level=logging.INFO,
|
|
38
|
+
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
|
39
|
+
)
|
|
40
|
+
logger = logging.getLogger('{{ logger_name }}')
|
|
41
|
+
|
|
42
|
+
# Get stage from environment (useful for different API endpoints)
|
|
43
|
+
STAGE = os.getenv("STAGE", "MAINNET").upper()
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
{% if requires_auth %}
|
|
47
|
+
class AuthMiddleware(Middleware):
|
|
48
|
+
"""Middleware to extract and store API keys from Authorization headers."""
|
|
49
|
+
|
|
50
|
+
async def on_request(self, context: MiddlewareContext, call_next):
|
|
51
|
+
"""Extract bearer token on each request and bind to context state."""
|
|
52
|
+
try:
|
|
53
|
+
# Access the raw HTTP request
|
|
54
|
+
request: Request = get_http_request()
|
|
55
|
+
|
|
56
|
+
# Debug: log all headers
|
|
57
|
+
logger.info(f"AuthMiddleware: Received request with headers: {dict(request.headers)}")
|
|
58
|
+
logger.info(f"AuthMiddleware: Method: {request.method}, URL: {request.url}")
|
|
59
|
+
|
|
60
|
+
# Extract bearer token from Authorization header
|
|
61
|
+
auth = request.headers.get("Authorization", "")
|
|
62
|
+
token = auth[7:].strip() if auth.lower().startswith("bearer ") else None
|
|
63
|
+
|
|
64
|
+
if not token:
|
|
65
|
+
# Check X-API-KEY header as alternative
|
|
66
|
+
token = request.headers.get("X-API-KEY", "")
|
|
67
|
+
|
|
68
|
+
if token:
|
|
69
|
+
# Store the API key in the context state
|
|
70
|
+
# This will be accessible in tools via get_context()
|
|
71
|
+
if hasattr(context, 'state'):
|
|
72
|
+
context.state.api_key = token
|
|
73
|
+
logger.info(f"API key bound to context state: {token[:10]}...")
|
|
74
|
+
else:
|
|
75
|
+
# Try to store it in the request state as fallback
|
|
76
|
+
request.state.api_key = token
|
|
77
|
+
logger.info(f"API key bound to request state: {token[:10]}...")
|
|
78
|
+
else:
|
|
79
|
+
logger.warning(f"No API key provided in request headers")
|
|
80
|
+
|
|
81
|
+
except Exception as e:
|
|
82
|
+
# This might happen in non-HTTP transports or if get_http_request fails
|
|
83
|
+
logger.debug(f"Could not extract API key from request: {e}")
|
|
84
|
+
|
|
85
|
+
# Proceed with the request (authentication check happens in the tools)
|
|
86
|
+
return await call_next(context)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
# Initialize FastMCP server with middleware
|
|
90
|
+
mcp = FastMCP("{{ api_name }} MCP Server", middleware=[AuthMiddleware()])
|
|
91
|
+
{% else %}
|
|
92
|
+
# Initialize FastMCP server
|
|
93
|
+
mcp = FastMCP("{{ api_name }} MCP Server")
|
|
94
|
+
{% endif %}
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
# Add health check endpoint using FastMCP's custom_route
|
|
98
|
+
@mcp.custom_route("/health", methods=["GET"])
|
|
99
|
+
async def health_check(request: Request) -> JSONResponse:
|
|
100
|
+
"""Health check endpoint for container orchestration."""
|
|
101
|
+
return JSONResponse(
|
|
102
|
+
content={
|
|
103
|
+
"status": "healthy",
|
|
104
|
+
"service": "{{ api_slug }}-mcp-server",
|
|
105
|
+
"timestamp": datetime.now().isoformat()
|
|
106
|
+
}
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
{% if requires_auth %}
|
|
111
|
+
def get_session_api_key(context: Context) -> Optional[str]:
|
|
112
|
+
"""Get the API key for the current session."""
|
|
113
|
+
try:
|
|
114
|
+
# Try to get the API key from the context state
|
|
115
|
+
# The middleware should have stored it there
|
|
116
|
+
if hasattr(context, 'state') and hasattr(context.state, 'api_key'):
|
|
117
|
+
return context.state.api_key
|
|
118
|
+
|
|
119
|
+
# Fallback: try to get it from the current HTTP request
|
|
120
|
+
try:
|
|
121
|
+
request: Request = get_http_request()
|
|
122
|
+
if hasattr(request.state, 'api_key'):
|
|
123
|
+
return request.state.api_key
|
|
124
|
+
except Exception:
|
|
125
|
+
pass
|
|
126
|
+
|
|
127
|
+
# If we're in a tool context, try to get the context using the dependency
|
|
128
|
+
try:
|
|
129
|
+
ctx = get_context()
|
|
130
|
+
if hasattr(ctx, 'state') and hasattr(ctx.state, 'api_key'):
|
|
131
|
+
return ctx.state.api_key
|
|
132
|
+
except Exception:
|
|
133
|
+
pass
|
|
134
|
+
|
|
135
|
+
except Exception as e:
|
|
136
|
+
logger.debug(f"Could not retrieve API key from context: {e}")
|
|
137
|
+
|
|
138
|
+
return None
|
|
139
|
+
{% endif %}
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
# TODO: Add your API-specific functions here
|
|
143
|
+
# Use @retry decorator ONLY for external API calls (not internal functions):
|
|
144
|
+
# @retry(tries=2, delay=1, backoff=2, jitter=(1, 3))
|
|
145
|
+
# def call_{{ api_slug }}_api(query: str, api_key: str) -> Dict[str, Any]:
|
|
146
|
+
# """Call the {{ api_name }} API with the given query."""
|
|
147
|
+
# # You can use STAGE to determine which API endpoint to use:
|
|
148
|
+
# # base_url = "https://api-testnet.example.com" if STAGE == "TESTNET" else "https://api.example.com"
|
|
149
|
+
# # Implement your API logic here (HTTP requests, SDK calls, etc.)
|
|
150
|
+
# pass
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
@mcp.tool()
|
|
154
|
+
async def example_tool(
|
|
155
|
+
context: Context,
|
|
156
|
+
query: str
|
|
157
|
+
) -> Dict[str, Any]:
|
|
158
|
+
"""
|
|
159
|
+
Example tool for {{ api_name }} API.
|
|
160
|
+
|
|
161
|
+
TODO: Replace this with your actual tool implementation.
|
|
162
|
+
|
|
163
|
+
Args:
|
|
164
|
+
context: MCP context (injected automatically)
|
|
165
|
+
query: Query parameter
|
|
166
|
+
|
|
167
|
+
Returns:
|
|
168
|
+
Dictionary with results
|
|
169
|
+
"""
|
|
170
|
+
{% if requires_auth %}
|
|
171
|
+
# Check for API key
|
|
172
|
+
api_key = get_session_api_key(context)
|
|
173
|
+
if not api_key:
|
|
174
|
+
return {"error": "No API key found. Please authenticate with Authorization: Bearer YOUR_API_KEY"}
|
|
175
|
+
{% endif %}
|
|
176
|
+
|
|
177
|
+
# TODO: Implement your tool logic here
|
|
178
|
+
return {
|
|
179
|
+
"status": "success",
|
|
180
|
+
"message": "This is a placeholder. Implement your {{ api_name }} logic here.",
|
|
181
|
+
"query": query,
|
|
182
|
+
"timestamp": datetime.now().isoformat()
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
@mcp.tool()
|
|
187
|
+
async def get_api_info(context: Context) -> Dict[str, Any]:
|
|
188
|
+
"""
|
|
189
|
+
Get information about the {{ api_name }} API service.
|
|
190
|
+
|
|
191
|
+
Args:
|
|
192
|
+
context: MCP context (injected automatically)
|
|
193
|
+
|
|
194
|
+
Returns:
|
|
195
|
+
Dictionary containing API information and status
|
|
196
|
+
"""
|
|
197
|
+
{% if requires_auth %}
|
|
198
|
+
# Check authentication status
|
|
199
|
+
api_key = get_session_api_key(context)
|
|
200
|
+
auth_status = "authenticated" if api_key else "not authenticated"
|
|
201
|
+
{% endif %}
|
|
202
|
+
|
|
203
|
+
return {
|
|
204
|
+
"status": "ready",
|
|
205
|
+
{% if requires_auth %}"auth_status": auth_status,{% endif %}
|
|
206
|
+
"api_name": "{{ api_name }}",
|
|
207
|
+
"api_url": "{{ api_url }}",
|
|
208
|
+
"documentation": "{{ docs_url }}",
|
|
209
|
+
"description": "{{ api_description|capitalize }}",
|
|
210
|
+
{% if requires_auth %}"authentication": "Bearer token required in Authorization header"{% else %}"authentication": "No authentication required"{% endif %}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def run_server():
|
|
215
|
+
"""Entry point for the executable script"""
|
|
216
|
+
logger.info("Starting {{ api_name }} MCP server{{ auth_description }}...")
|
|
217
|
+
{% if requires_auth %}
|
|
218
|
+
logger.info("Authentication: Clients must provide Authorization: Bearer YOUR_API_KEY")
|
|
219
|
+
{% endif %}
|
|
220
|
+
|
|
221
|
+
# Get configuration from environment
|
|
222
|
+
port = int(os.getenv("PORT", "8000"))
|
|
223
|
+
log_level = os.getenv("LOG_LEVEL", "INFO")
|
|
224
|
+
logger.info(f"Server will listen on port {port}")
|
|
225
|
+
|
|
226
|
+
try:
|
|
227
|
+
mcp.run(
|
|
228
|
+
transport="streamable-http",
|
|
229
|
+
port=port,
|
|
230
|
+
host="0.0.0.0",
|
|
231
|
+
path="/mcp",
|
|
232
|
+
log_level=log_level.lower()
|
|
233
|
+
)
|
|
234
|
+
except Exception as e:
|
|
235
|
+
logger.error(f"Error starting MCP server: {e}")
|
|
236
|
+
raise
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
if __name__ == "__main__":
|
|
240
|
+
run_server()
|