traia-iatp 0.1.29__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.

Files changed (107) hide show
  1. traia_iatp/README.md +368 -0
  2. traia_iatp/__init__.py +54 -0
  3. traia_iatp/cli/__init__.py +5 -0
  4. traia_iatp/cli/main.py +483 -0
  5. traia_iatp/client/__init__.py +10 -0
  6. traia_iatp/client/a2a_client.py +274 -0
  7. traia_iatp/client/crewai_a2a_tools.py +335 -0
  8. traia_iatp/client/d402_a2a_client.py +293 -0
  9. traia_iatp/client/grpc_a2a_tools.py +349 -0
  10. traia_iatp/client/root_path_a2a_client.py +1 -0
  11. traia_iatp/contracts/__init__.py +12 -0
  12. traia_iatp/contracts/iatp_contracts_config.py +263 -0
  13. traia_iatp/contracts/wallet_creator.py +255 -0
  14. traia_iatp/core/__init__.py +43 -0
  15. traia_iatp/core/models.py +172 -0
  16. traia_iatp/d402/__init__.py +55 -0
  17. traia_iatp/d402/chains.py +102 -0
  18. traia_iatp/d402/client.py +150 -0
  19. traia_iatp/d402/clients/__init__.py +7 -0
  20. traia_iatp/d402/clients/base.py +218 -0
  21. traia_iatp/d402/clients/httpx.py +219 -0
  22. traia_iatp/d402/common.py +114 -0
  23. traia_iatp/d402/encoding.py +28 -0
  24. traia_iatp/d402/examples/client_example.py +197 -0
  25. traia_iatp/d402/examples/server_example.py +171 -0
  26. traia_iatp/d402/facilitator.py +453 -0
  27. traia_iatp/d402/fastapi_middleware/__init__.py +6 -0
  28. traia_iatp/d402/fastapi_middleware/middleware.py +225 -0
  29. traia_iatp/d402/fastmcp_middleware.py +147 -0
  30. traia_iatp/d402/mcp_middleware.py +434 -0
  31. traia_iatp/d402/middleware.py +193 -0
  32. traia_iatp/d402/models.py +116 -0
  33. traia_iatp/d402/networks.py +98 -0
  34. traia_iatp/d402/path.py +43 -0
  35. traia_iatp/d402/payment_introspection.py +104 -0
  36. traia_iatp/d402/payment_signing.py +178 -0
  37. traia_iatp/d402/paywall.py +119 -0
  38. traia_iatp/d402/starlette_middleware.py +326 -0
  39. traia_iatp/d402/template.py +1 -0
  40. traia_iatp/d402/types.py +300 -0
  41. traia_iatp/mcp/__init__.py +18 -0
  42. traia_iatp/mcp/client.py +201 -0
  43. traia_iatp/mcp/d402_mcp_tool_adapter.py +361 -0
  44. traia_iatp/mcp/mcp_agent_template.py +481 -0
  45. traia_iatp/mcp/templates/Dockerfile.j2 +80 -0
  46. traia_iatp/mcp/templates/README.md.j2 +310 -0
  47. traia_iatp/mcp/templates/cursor-rules.md.j2 +520 -0
  48. traia_iatp/mcp/templates/deployment_params.json.j2 +20 -0
  49. traia_iatp/mcp/templates/docker-compose.yml.j2 +32 -0
  50. traia_iatp/mcp/templates/dockerignore.j2 +47 -0
  51. traia_iatp/mcp/templates/env.example.j2 +57 -0
  52. traia_iatp/mcp/templates/gitignore.j2 +77 -0
  53. traia_iatp/mcp/templates/mcp_health_check.py.j2 +150 -0
  54. traia_iatp/mcp/templates/pyproject.toml.j2 +32 -0
  55. traia_iatp/mcp/templates/pyrightconfig.json.j2 +22 -0
  56. traia_iatp/mcp/templates/run_local_docker.sh.j2 +390 -0
  57. traia_iatp/mcp/templates/server.py.j2 +175 -0
  58. traia_iatp/mcp/traia_mcp_adapter.py +543 -0
  59. traia_iatp/preview_diagrams.html +181 -0
  60. traia_iatp/registry/__init__.py +26 -0
  61. traia_iatp/registry/atlas_search_indexes.json +280 -0
  62. traia_iatp/registry/embeddings.py +298 -0
  63. traia_iatp/registry/iatp_search_api.py +846 -0
  64. traia_iatp/registry/mongodb_registry.py +771 -0
  65. traia_iatp/registry/readmes/ATLAS_SEARCH_INDEXES.md +252 -0
  66. traia_iatp/registry/readmes/ATLAS_SEARCH_SETUP.md +134 -0
  67. traia_iatp/registry/readmes/AUTHENTICATION_UPDATE.md +124 -0
  68. traia_iatp/registry/readmes/EMBEDDINGS_SETUP.md +172 -0
  69. traia_iatp/registry/readmes/IATP_SEARCH_API_GUIDE.md +257 -0
  70. traia_iatp/registry/readmes/MONGODB_X509_AUTH.md +208 -0
  71. traia_iatp/registry/readmes/README.md +251 -0
  72. traia_iatp/registry/readmes/REFACTORING_SUMMARY.md +191 -0
  73. traia_iatp/scripts/__init__.py +2 -0
  74. traia_iatp/scripts/create_wallet.py +244 -0
  75. traia_iatp/server/__init__.py +15 -0
  76. traia_iatp/server/a2a_server.py +219 -0
  77. traia_iatp/server/example_template_usage.py +72 -0
  78. traia_iatp/server/iatp_server_agent_generator.py +237 -0
  79. traia_iatp/server/iatp_server_template_generator.py +235 -0
  80. traia_iatp/server/templates/.dockerignore.j2 +48 -0
  81. traia_iatp/server/templates/Dockerfile.j2 +49 -0
  82. traia_iatp/server/templates/README.md +137 -0
  83. traia_iatp/server/templates/README.md.j2 +425 -0
  84. traia_iatp/server/templates/__init__.py +1 -0
  85. traia_iatp/server/templates/__main__.py.j2 +565 -0
  86. traia_iatp/server/templates/agent.py.j2 +94 -0
  87. traia_iatp/server/templates/agent_config.json.j2 +22 -0
  88. traia_iatp/server/templates/agent_executor.py.j2 +279 -0
  89. traia_iatp/server/templates/docker-compose.yml.j2 +23 -0
  90. traia_iatp/server/templates/env.example.j2 +84 -0
  91. traia_iatp/server/templates/gitignore.j2 +78 -0
  92. traia_iatp/server/templates/grpc_server.py.j2 +218 -0
  93. traia_iatp/server/templates/pyproject.toml.j2 +78 -0
  94. traia_iatp/server/templates/run_local_docker.sh.j2 +103 -0
  95. traia_iatp/server/templates/server.py.j2 +243 -0
  96. traia_iatp/special_agencies/__init__.py +4 -0
  97. traia_iatp/special_agencies/registry_search_agency.py +392 -0
  98. traia_iatp/utils/__init__.py +10 -0
  99. traia_iatp/utils/docker_utils.py +251 -0
  100. traia_iatp/utils/general.py +64 -0
  101. traia_iatp/utils/iatp_utils.py +126 -0
  102. traia_iatp-0.1.29.dist-info/METADATA +423 -0
  103. traia_iatp-0.1.29.dist-info/RECORD +107 -0
  104. traia_iatp-0.1.29.dist-info/WHEEL +5 -0
  105. traia_iatp-0.1.29.dist-info/entry_points.txt +2 -0
  106. traia_iatp-0.1.29.dist-info/licenses/LICENSE +21 -0
  107. traia_iatp-0.1.29.dist-info/top_level.txt +1 -0
@@ -0,0 +1,77 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.so
6
+ .Python
7
+ build/
8
+ develop-eggs/
9
+ dist/
10
+ downloads/
11
+ eggs/
12
+ .eggs/
13
+ lib/
14
+ lib64/
15
+ parts/
16
+ sdist/
17
+ var/
18
+ wheels/
19
+ *.egg-info/
20
+ .installed.cfg
21
+ *.egg
22
+
23
+ # Virtual Environment
24
+ venv/
25
+ ENV/
26
+ env/
27
+ .venv/
28
+
29
+ # IDE
30
+ .vscode/
31
+ .idea/
32
+ *.swp
33
+ *.swo
34
+ *~
35
+
36
+ # Environment variables
37
+ .env
38
+ .env.local
39
+ .env.*.local
40
+
41
+ # OS
42
+ .DS_Store
43
+ Thumbs.db
44
+
45
+ # Logs
46
+ *.log
47
+ logs/
48
+
49
+ # Testing
50
+ .pytest_cache/
51
+ .coverage
52
+ htmlcov/
53
+ .tox/
54
+ .hypothesis/
55
+
56
+ # Package managers
57
+ uv.lock
58
+ poetry.lock
59
+ pip-log.txt
60
+
61
+ # Docker
62
+ .dockerignore
63
+
64
+ # Output files
65
+ output_tests/
66
+ *.txt
67
+ *.json
68
+
69
+ # Temporary files
70
+ *.tmp
71
+ *.temp
72
+ *.bak
73
+
74
+ # {{ api_name }} specific
75
+ {% if additional_gitignore_entries -%}
76
+ {{ additional_gitignore_entries }}
77
+ {%- endif %}
@@ -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,32 @@
1
+ [project]
2
+ name = "{{ api_slug }}-mcp-server"
3
+ version = "0.1.0"
4
+ description = "MCP server for {{ api_name }} API with {% if auth_description %} authentication and {% endif %} HTTP 402 payment protocol support"
5
+ requires-python = ">=3.12"
6
+ dependencies = [
7
+ "anyio>=4.0.0",
8
+ "mcp>=1.1.2",
9
+ "python-dotenv>=1.1.1",
10
+ "requests>=2.32.5",
11
+ "starlette>=0.45.0",
12
+ "retry>=0.9.2",
13
+ "traia-iatp>=0.1.27", # For d402 payment protocol support
14
+ "uvicorn>=0.37.0",
15
+ "web3>=6.15.0", # For blockchain payment verification
16
+ {% if sdk_package %}
17
+ "{{ sdk_package }}",
18
+ {% endif %}
19
+ ]
20
+
21
+ [build-system]
22
+ requires = ["hatchling"]
23
+ build-backend = "hatchling.build"
24
+
25
+ [tool.hatch.metadata]
26
+ allow-direct-references = true
27
+
28
+ [tool.hatch.build.targets.wheel]
29
+ include = [
30
+ "server.py",
31
+ "mcp_health_check.py",
32
+ ]
@@ -0,0 +1,22 @@
1
+ {
2
+ "include": [
3
+ "server.py",
4
+ "mcp_health_check.py"
5
+ ],
6
+ "exclude": [
7
+ "**/__pycache__",
8
+ ".venv",
9
+ "**/*.pyc"
10
+ ],
11
+ "extraPaths": [
12
+ "../../../../IATP/src",
13
+ "/Users/eitanlavi/workspace/traia/IATP/src"
14
+ ],
15
+ "pythonVersion": "3.12",
16
+ "typeCheckingMode": "basic",
17
+ "reportMissingImports": true,
18
+ "reportMissingTypeStubs": false,
19
+ "venvPath": ".",
20
+ "venv": ".venv"
21
+ }
22
+
@@ -0,0 +1,390 @@
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 (will be overridden by .env if present)
15
+ IMAGE_NAME="{{ api_slug }}-mcp-server"
16
+ CONTAINER_NAME="{{ api_slug }}-mcp-local"
17
+ HOST_PORT=${PORT:-8000}
18
+ CONTAINER_PORT=${PORT:-8000}
19
+
20
+ echo -e "${BLUE}๐Ÿš€ Building and running {{ api_name }} MCP Server...${NC}"
21
+ echo
22
+
23
+ # Setup .env file for local development
24
+ # Step 1: Copy .env.example to .env (if .env doesn't exist)
25
+ if [ ! -f .env ]; then
26
+ echo -e "${BLUE}๐Ÿ“‹ Setting up .env file for local development...${NC}"
27
+
28
+ if [ -f .env.example ]; then
29
+ echo -e "${BLUE} Copying .env.example to .env...${NC}"
30
+ cp .env.example .env
31
+ else
32
+ echo -e "${YELLOW}โš ๏ธ .env.example not found, creating minimal .env template...${NC}"
33
+ # Create minimal .env template
34
+ cat > .env << EOF
35
+ # {{ api_name }} MCP Server Configuration
36
+ PORT=8000
37
+ STAGE=MAINNET
38
+ LOG_LEVEL=INFO
39
+
40
+ {% if requires_auth %}
41
+ # API Authentication (server's internal key for payment mode)
42
+ {{ api_key_env_var }}=
43
+ {% endif %}
44
+ # D402 Payment Protocol Configuration
45
+ SERVER_ADDRESS=
46
+ MCP_OPERATOR_PRIVATE_KEY=
47
+ MCP_OPERATOR_ADDRESS=
48
+ D402_FACILITATOR_URL=https://facilitator.d402.net
49
+ D402_FACILITATOR_API_KEY=
50
+ DEFAULT_SETTLEMENT_TOKEN=0x036CbD53842c5426634e7929541eC2318f3dCF7e
51
+ DEFAULT_SETTLEMENT_NETWORK=sepolia
52
+ D402_TESTING_MODE=true
53
+ EOF
54
+ fi
55
+ fi
56
+
57
+ # Step 2: Fix pyproject.toml to use local IATP path for account generation
58
+ # This is needed because uv run python requires a valid pyproject.toml
59
+ echo -e "${BLUE}๐Ÿ”ง Fixing pyproject.toml for local development...${NC}"
60
+
61
+ # Find local IATP path
62
+ IATP_PATH=""
63
+ if [ -d "/Users/eitanlavi/workspace/traia/IATP" ]; then
64
+ IATP_PATH="/Users/eitanlavi/workspace/traia/IATP"
65
+ elif [ -d "$(dirname $(pwd))/IATP" ]; then
66
+ IATP_PATH="$(dirname $(pwd))/IATP"
67
+ elif [ -d "$HOME/workspace/traia/IATP" ]; then
68
+ IATP_PATH="$HOME/workspace/traia/IATP"
69
+ elif [ -n "$LOCAL_IATP_PATH" ] && [ -d "$LOCAL_IATP_PATH" ]; then
70
+ IATP_PATH="$LOCAL_IATP_PATH"
71
+ fi
72
+
73
+ # Temporarily update pyproject.toml to use local IATP path (if /tmp/IATP is present)
74
+ if grep -q "file:///tmp/IATP" pyproject.toml 2>/dev/null; then
75
+ if [ -n "$IATP_PATH" ] && [ -d "$IATP_PATH" ]; then
76
+ echo -e "${BLUE} Updating pyproject.toml to use: $IATP_PATH${NC}"
77
+ if [[ "$OSTYPE" == "darwin"* ]]; then
78
+ sed -i '' "s|file:///tmp/IATP|file://$IATP_PATH|g" pyproject.toml
79
+ else
80
+ sed -i "s|file:///tmp/IATP|file://$IATP_PATH|g" pyproject.toml
81
+ fi
82
+ else
83
+ echo -e "${RED}โŒ Local IATP not found. Cannot generate accounts.${NC}"
84
+ echo -e "${YELLOW} Please set LOCAL_IATP_PATH environment variable or ensure IATP is at /Users/eitanlavi/workspace/traia/IATP${NC}"
85
+ exit 1
86
+ fi
87
+ fi
88
+
89
+ # Step 3: Generate Ethereum accounts for local development (workaround)
90
+ # Always regenerate keys for local dev to ensure they're set
91
+ echo -e "${BLUE}๐Ÿ” Generating Ethereum accounts for local testing (workaround for local dev)...${NC}"
92
+
93
+ # Generate accounts using Python (web3 library)
94
+ # Use uv run to ensure web3 is available from the project dependencies
95
+ uv run python << 'PYTHON_SCRIPT'
96
+ import sys
97
+ import secrets
98
+
99
+ try:
100
+ from web3 import Web3
101
+
102
+ # Generate server address (EOA for local testing)
103
+ # This is a workaround - in production, SERVER_ADDRESS comes from IATP wallet contract
104
+ server_private_key = "0x" + secrets.token_hex(32)
105
+ server_account = Web3().eth.account.from_key(server_private_key)
106
+ server_address = server_account.address
107
+
108
+ # Generate operator private key and address
109
+ # This is a workaround - in production, these come from IATP wallet contract
110
+ operator_private_key = "0x" + secrets.token_hex(32)
111
+ operator_account = Web3().eth.account.from_key(operator_private_key)
112
+ operator_address = operator_account.address
113
+
114
+ # Write to temporary file
115
+ with open('.env.generated', 'w') as f:
116
+ f.write(f"SERVER_ADDRESS={server_address}\n")
117
+ f.write(f"MCP_OPERATOR_PRIVATE_KEY={operator_private_key}\n")
118
+ f.write(f"MCP_OPERATOR_ADDRESS={operator_address}\n")
119
+
120
+ print(f"Generated SERVER_ADDRESS: {server_address}")
121
+ print(f"Generated MCP_OPERATOR_ADDRESS: {operator_address}")
122
+ sys.exit(0)
123
+ except ImportError:
124
+ print("ERROR: web3 library not found. Install it with: pip install web3", file=sys.stderr)
125
+ sys.exit(1)
126
+ except Exception as e:
127
+ print(f"ERROR: Failed to generate accounts: {e}", file=sys.stderr)
128
+ sys.exit(1)
129
+ PYTHON_SCRIPT
130
+
131
+ # Step 4: Fill in generated values into .env
132
+ if [ $? -eq 0 ] && [ -f .env.generated ]; then
133
+ # Load generated values
134
+ source .env.generated
135
+
136
+ # Update .env file with generated values (macOS-compatible sed)
137
+ # Use a temporary file approach for portability
138
+ if grep -q "^SERVER_ADDRESS=" .env; then
139
+ if [[ "$OSTYPE" == "darwin"* ]]; then
140
+ sed -i '' "s|^SERVER_ADDRESS=.*|SERVER_ADDRESS=$SERVER_ADDRESS|" .env
141
+ else
142
+ sed -i "s|^SERVER_ADDRESS=.*|SERVER_ADDRESS=$SERVER_ADDRESS|" .env
143
+ fi
144
+ else
145
+ echo "SERVER_ADDRESS=$SERVER_ADDRESS" >> .env
146
+ fi
147
+
148
+ if grep -q "^MCP_OPERATOR_PRIVATE_KEY=" .env; then
149
+ if [[ "$OSTYPE" == "darwin"* ]]; then
150
+ sed -i '' "s|^MCP_OPERATOR_PRIVATE_KEY=.*|MCP_OPERATOR_PRIVATE_KEY=$MCP_OPERATOR_PRIVATE_KEY|" .env
151
+ else
152
+ sed -i "s|^MCP_OPERATOR_PRIVATE_KEY=.*|MCP_OPERATOR_PRIVATE_KEY=$MCP_OPERATOR_PRIVATE_KEY|" .env
153
+ fi
154
+ else
155
+ echo "MCP_OPERATOR_PRIVATE_KEY=$MCP_OPERATOR_PRIVATE_KEY" >> .env
156
+ fi
157
+
158
+ if grep -q "^MCP_OPERATOR_ADDRESS=" .env; then
159
+ if [[ "$OSTYPE" == "darwin"* ]]; then
160
+ sed -i '' "s|^MCP_OPERATOR_ADDRESS=.*|MCP_OPERATOR_ADDRESS=$MCP_OPERATOR_ADDRESS|" .env
161
+ else
162
+ sed -i "s|^MCP_OPERATOR_ADDRESS=.*|MCP_OPERATOR_ADDRESS=$MCP_OPERATOR_ADDRESS|" .env
163
+ fi
164
+ else
165
+ echo "MCP_OPERATOR_ADDRESS=$MCP_OPERATOR_ADDRESS" >> .env
166
+ fi
167
+
168
+ # Set D402_TESTING_MODE if not set
169
+ if ! grep -q "^D402_TESTING_MODE=" .env; then
170
+ echo "D402_TESTING_MODE=true" >> .env
171
+ else
172
+ if [[ "$OSTYPE" == "darwin"* ]]; then
173
+ sed -i '' "s|^D402_TESTING_MODE=.*|D402_TESTING_MODE=true|" .env
174
+ else
175
+ sed -i "s|^D402_TESTING_MODE=.*|D402_TESTING_MODE=true|" .env
176
+ fi
177
+ fi
178
+
179
+ rm -f .env.generated
180
+ echo -e "${GREEN}โœ… Generated Ethereum accounts and updated .env${NC}"
181
+ else
182
+ echo -e "${RED}โŒ Failed to generate accounts. Please install web3: pip install web3${NC}"
183
+ echo -e "${YELLOW} Or set SERVER_ADDRESS, MCP_OPERATOR_PRIVATE_KEY, and MCP_OPERATOR_ADDRESS manually in .env${NC}"
184
+ exit 1
185
+ fi
186
+
187
+ # Step 5: Prompt for API key if required (only if not already set)
188
+ {% if requires_auth %}
189
+ if ! grep -q "^{{ api_key_env_var }}=[^[:space:]]" .env; then
190
+ if [ -t 0 ]; then
191
+ echo ""
192
+ echo -e "${YELLOW}๐Ÿ“ Please enter your {{ api_name }} API key:${NC}"
193
+ echo -e "${YELLOW} (This is the server's internal API key used when clients pay via 402)${NC}"
194
+ read -p "{{ api_key_env_var }}: " API_KEY_VALUE
195
+ if [ -n "$API_KEY_VALUE" ]; then
196
+ if grep -q "^{{ api_key_env_var }}=" .env; then
197
+ if [[ "$OSTYPE" == "darwin"* ]]; then
198
+ sed -i '' "s|^{{ api_key_env_var }}=.*|{{ api_key_env_var }}=$API_KEY_VALUE|" .env
199
+ else
200
+ sed -i "s|^{{ api_key_env_var }}=.*|{{ api_key_env_var }}=$API_KEY_VALUE|" .env
201
+ fi
202
+ else
203
+ echo "{{ api_key_env_var }}=$API_KEY_VALUE" >> .env
204
+ fi
205
+ echo -e "${GREEN}โœ… API key saved to .env${NC}"
206
+ else
207
+ echo -e "${YELLOW}โš ๏ธ No API key provided. You can set it later in .env${NC}"
208
+ fi
209
+ else
210
+ echo -e "${YELLOW}โš ๏ธ Non-interactive mode: Please set {{ api_key_env_var }} in .env manually${NC}"
211
+ fi
212
+ else
213
+ echo -e "${GREEN}โœ… {{ api_key_env_var }} already set in .env${NC}"
214
+ fi
215
+ {% endif %}
216
+
217
+ echo ""
218
+ echo -e "${GREEN}โœ… .env file configured for local development!${NC}"
219
+ echo -e "${BLUE}๐Ÿ“‹ Review .env file if needed before continuing...${NC}"
220
+ if [ -t 0 ]; then
221
+ echo -e "${YELLOW}Press Enter to continue, or Ctrl+C to cancel...${NC}"
222
+ read
223
+ fi
224
+
225
+ # Load environment variables from .env file
226
+ echo -e "${BLUE}๐Ÿ“‹ Loading environment variables from .env file...${NC}"
227
+ set -a # Export all variables
228
+ source .env
229
+ set +a # Stop exporting
230
+
231
+ # Update port variables after loading .env
232
+ HOST_PORT=${PORT:-8000}
233
+ CONTAINER_PORT=${PORT:-8000}
234
+
235
+ # Check if Docker is installed
236
+ if ! command -v docker &> /dev/null; then
237
+ echo -e "${RED}โŒ Docker is not installed. Please install Docker first.${NC}"
238
+ exit 1
239
+ fi
240
+
241
+ # Stop and remove existing container if it exists
242
+ if docker ps -a | grep -q $CONTAINER_NAME; then
243
+ echo -e "${YELLOW}๐Ÿ›‘ Stopping existing container...${NC}"
244
+ docker stop $CONTAINER_NAME >/dev/null 2>&1 || true
245
+ docker rm $CONTAINER_NAME >/dev/null 2>&1 || true
246
+ fi
247
+
248
+ # Build the Docker image
249
+ echo -e "${BLUE}๐Ÿ”จ Building Docker image...${NC}"
250
+
251
+ # Check if pyproject.toml uses local IATP path (for local development)
252
+ if grep -q "file://" pyproject.toml 2>/dev/null; then
253
+ # Extract IATP path from pyproject.toml (macOS-compatible, no Perl regex)
254
+ IATP_PATH_FROM_TOML=$(grep "file://" pyproject.toml | sed -E 's|.*file://([^"]+).*|\1|' | head -1)
255
+
256
+ # If path is /tmp/IATP (Docker path), find the actual local IATP path
257
+ if [ "$IATP_PATH_FROM_TOML" = "/tmp/IATP" ] || [ ! -d "$IATP_PATH_FROM_TOML" ]; then
258
+ # Try to find local IATP path (common locations)
259
+ if [ -d "/Users/eitanlavi/workspace/traia/IATP" ]; then
260
+ IATP_PATH="/Users/eitanlavi/workspace/traia/IATP"
261
+ elif [ -d "$(dirname $(pwd))/IATP" ]; then
262
+ IATP_PATH="$(dirname $(pwd))/IATP"
263
+ elif [ -d "$HOME/workspace/traia/IATP" ]; then
264
+ IATP_PATH="$HOME/workspace/traia/IATP"
265
+ elif [ -n "$LOCAL_IATP_PATH" ] && [ -d "$LOCAL_IATP_PATH" ]; then
266
+ IATP_PATH="$LOCAL_IATP_PATH"
267
+ else
268
+ IATP_PATH=""
269
+ fi
270
+ else
271
+ IATP_PATH="$IATP_PATH_FROM_TOML"
272
+ fi
273
+
274
+ if [ -n "$IATP_PATH" ] && [ -d "$IATP_PATH" ]; then
275
+ echo -e "${BLUE}๐Ÿ“ฆ Using local IATP package: $IATP_PATH${NC}"
276
+ # Copy IATP into build context for Docker (exclude .venv to save space!)
277
+ mkdir -p .docker-iatp
278
+
279
+ # Use rsync if available (much better), otherwise cp with find
280
+ if command -v rsync &> /dev/null; then
281
+ rsync -a --exclude='.venv' --exclude='__pycache__' --exclude='.git' --exclude='build' --exclude='dist' --exclude='*.egg-info' "$IATP_PATH/" .docker-iatp/IATP/
282
+ echo -e "${BLUE} Copied IATP to build context (excluded .venv, ${GREEN}saved ~900MB${BLUE})${NC}"
283
+ else
284
+ # Fallback: use cp but warn about size
285
+ cp -r "$IATP_PATH" .docker-iatp/IATP
286
+ # Try to remove .venv after copy
287
+ rm -rf .docker-iatp/IATP/.venv .docker-iatp/IATP/__pycache__ .docker-iatp/IATP/.git 2>/dev/null
288
+ echo -e "${BLUE} Copied IATP to build context (cleaned .venv after copy)${NC}"
289
+ fi
290
+ # Temporarily update pyproject.toml to use Docker path
291
+ if [[ "$OSTYPE" == "darwin"* ]]; then
292
+ sed -i.bak 's|file://.*IATP|file:///tmp/IATP|g' pyproject.toml
293
+ else
294
+ sed -i.bak 's|file://.*IATP|file:///tmp/IATP|g' pyproject.toml
295
+ fi
296
+ docker build --no-cache -t $IMAGE_NAME .
297
+ BUILD_EXIT_CODE=$?
298
+ # Restore original pyproject.toml
299
+ mv pyproject.toml.bak pyproject.toml
300
+ rm -rf .docker-iatp
301
+ if [ $BUILD_EXIT_CODE -ne 0 ]; then
302
+ exit $BUILD_EXIT_CODE
303
+ fi
304
+ else
305
+ echo -e "${YELLOW}โš ๏ธ Local IATP path not found${NC}"
306
+ echo -e "${YELLOW} Falling back to published version (may fail if not published)${NC}"
307
+ # If pyproject.toml has /tmp/IATP, replace with published version
308
+ if grep -q "file:///tmp/IATP" pyproject.toml 2>/dev/null; then
309
+ if [[ "$OSTYPE" == "darwin"* ]]; then
310
+ sed -i.bak 's|file:///tmp/IATP|traia-iatp>=0.1.27|g' pyproject.toml
311
+ else
312
+ sed -i.bak 's|file:///tmp/IATP|traia-iatp>=0.1.27|g' pyproject.toml
313
+ fi
314
+ docker build --no-cache -t $IMAGE_NAME .
315
+ BUILD_EXIT_CODE=$?
316
+ mv pyproject.toml.bak pyproject.toml
317
+ if [ $BUILD_EXIT_CODE -ne 0 ]; then
318
+ exit $BUILD_EXIT_CODE
319
+ fi
320
+ else
321
+ docker build --no-cache -t $IMAGE_NAME .
322
+ fi
323
+ fi
324
+ else
325
+ # Using published version, normal build
326
+ docker build --no-cache -t $IMAGE_NAME .
327
+ fi
328
+
329
+ # Run the container
330
+ echo -e "${BLUE}๐Ÿƒ Starting container...${NC}"
331
+ # Use --env-file to load all variables from .env file
332
+ docker run -d \
333
+ --name $CONTAINER_NAME \
334
+ -p $HOST_PORT:$CONTAINER_PORT \
335
+ --env-file .env \
336
+ $IMAGE_NAME
337
+
338
+ # Wait for the server to start
339
+ echo -e "${YELLOW}โณ Waiting for server to start...${NC}"
340
+ sleep 3
341
+
342
+ # Check if container is running
343
+ if ! docker ps | grep -q $CONTAINER_NAME; then
344
+ echo -e "${RED}โŒ Container failed to start. Checking logs:${NC}"
345
+ docker logs $CONTAINER_NAME
346
+ exit 1
347
+ fi
348
+
349
+ # Get container info
350
+ CONTAINER_ID=$(docker ps -q -f name=$CONTAINER_NAME)
351
+ CONTAINER_IP=$(docker inspect -f '{% raw %}{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}{% endraw %}' $CONTAINER_ID)
352
+
353
+ # Output connection information
354
+ echo
355
+ echo -e "${GREEN}โœ… {{ api_name }} MCP Server is running!${NC}"
356
+ echo
357
+ echo -e "${BLUE}๐Ÿ“ Connection Information:${NC}"
358
+ echo -e " Local URL: ${GREEN}http://localhost:${HOST_PORT}/mcp${NC}"
359
+ echo -e " Container IP: ${GREEN}${CONTAINER_IP}:${CONTAINER_PORT}${NC}"
360
+ echo -e " Container Name: ${GREEN}${CONTAINER_NAME}${NC}"
361
+ echo -e " Container ID: ${GREEN}${CONTAINER_ID:0:12}${NC}"
362
+ echo
363
+ echo -e "${BLUE}๐Ÿ“ Useful commands:${NC}"
364
+ echo -e " View logs: ${YELLOW}docker logs -f ${CONTAINER_NAME}${NC}"
365
+ echo -e " Stop server: ${YELLOW}docker stop ${CONTAINER_NAME}${NC}"
366
+ echo -e " Remove container: ${YELLOW}docker rm ${CONTAINER_NAME}${NC}"
367
+ echo -e " Shell access: ${YELLOW}docker exec -it ${CONTAINER_NAME} /bin/bash${NC}"
368
+ echo
369
+ echo -e "${BLUE}๐Ÿ”Œ MCP Server Endpoint:${NC}"
370
+ echo -e " ${GREEN}http://localhost:${HOST_PORT}/mcp${NC}"
371
+ echo
372
+
373
+ # Check if the server is responding
374
+ echo -e "${YELLOW}๐Ÿ” Checking server health...${NC}"
375
+ if curl -s -o /dev/null -w "%{http_code}" "http://localhost:${HOST_PORT}/mcp" | grep -q "200\|404\|405"; then
376
+ echo -e "${GREEN}โœ… Server is responding!${NC}"
377
+ else
378
+ echo -e "${YELLOW}โš ๏ธ Server may still be starting up. Check logs with: docker logs -f ${CONTAINER_NAME}${NC}"
379
+ fi
380
+
381
+ # Cleanup: Restore pyproject.toml to use /tmp/IATP (git-committed version)
382
+ echo -e "${BLUE}๐Ÿงน Cleaning up...${NC}"
383
+ if grep -q "file:///Users/eitanlavi/workspace/traia/IATP" pyproject.toml 2>/dev/null; then
384
+ if [[ "$OSTYPE" == "darwin"* ]]; then
385
+ sed -i '' "s|file:///Users/eitanlavi/workspace/traia/IATP|file:///tmp/IATP|g" pyproject.toml
386
+ else
387
+ sed -i "s|file:///Users/eitanlavi/workspace/traia/IATP|file:///tmp/IATP|g" pyproject.toml
388
+ fi
389
+ echo -e "${GREEN}โœ… Restored pyproject.toml to git state${NC}"
390
+ fi