naas-abi-core 1.4.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.
- assets/favicon.ico +0 -0
- assets/logo.png +0 -0
- naas_abi_core/__init__.py +1 -0
- naas_abi_core/apps/api/api.py +245 -0
- naas_abi_core/apps/api/api_test.py +281 -0
- naas_abi_core/apps/api/openapi_doc.py +144 -0
- naas_abi_core/apps/mcp/Dockerfile.mcp +35 -0
- naas_abi_core/apps/mcp/mcp_server.py +243 -0
- naas_abi_core/apps/mcp/mcp_server_test.py +163 -0
- naas_abi_core/apps/terminal_agent/main.py +555 -0
- naas_abi_core/apps/terminal_agent/terminal_style.py +175 -0
- naas_abi_core/engine/Engine.py +87 -0
- naas_abi_core/engine/EngineProxy.py +109 -0
- naas_abi_core/engine/Engine_test.py +6 -0
- naas_abi_core/engine/IEngine.py +91 -0
- naas_abi_core/engine/conftest.py +45 -0
- naas_abi_core/engine/engine_configuration/EngineConfiguration.py +216 -0
- naas_abi_core/engine/engine_configuration/EngineConfiguration_Deploy.py +7 -0
- naas_abi_core/engine/engine_configuration/EngineConfiguration_GenericLoader.py +49 -0
- naas_abi_core/engine/engine_configuration/EngineConfiguration_ObjectStorageService.py +159 -0
- naas_abi_core/engine/engine_configuration/EngineConfiguration_ObjectStorageService_test.py +26 -0
- naas_abi_core/engine/engine_configuration/EngineConfiguration_SecretService.py +138 -0
- naas_abi_core/engine/engine_configuration/EngineConfiguration_SecretService_test.py +74 -0
- naas_abi_core/engine/engine_configuration/EngineConfiguration_TripleStoreService.py +224 -0
- naas_abi_core/engine/engine_configuration/EngineConfiguration_TripleStoreService_test.py +109 -0
- naas_abi_core/engine/engine_configuration/EngineConfiguration_VectorStoreService.py +76 -0
- naas_abi_core/engine/engine_configuration/EngineConfiguration_VectorStoreService_test.py +33 -0
- naas_abi_core/engine/engine_configuration/EngineConfiguration_test.py +9 -0
- naas_abi_core/engine/engine_configuration/utils/PydanticModelValidator.py +15 -0
- naas_abi_core/engine/engine_loaders/EngineModuleLoader.py +302 -0
- naas_abi_core/engine/engine_loaders/EngineOntologyLoader.py +16 -0
- naas_abi_core/engine/engine_loaders/EngineServiceLoader.py +47 -0
- naas_abi_core/integration/__init__.py +7 -0
- naas_abi_core/integration/integration.py +28 -0
- naas_abi_core/models/Model.py +198 -0
- naas_abi_core/models/OpenRouter.py +18 -0
- naas_abi_core/models/OpenRouter_test.py +36 -0
- naas_abi_core/module/Module.py +252 -0
- naas_abi_core/module/ModuleAgentLoader.py +50 -0
- naas_abi_core/module/ModuleUtils.py +20 -0
- naas_abi_core/modules/templatablesparqlquery/README.md +196 -0
- naas_abi_core/modules/templatablesparqlquery/__init__.py +39 -0
- naas_abi_core/modules/templatablesparqlquery/ontologies/TemplatableSparqlQueryOntology.ttl +116 -0
- naas_abi_core/modules/templatablesparqlquery/workflows/GenericWorkflow.py +48 -0
- naas_abi_core/modules/templatablesparqlquery/workflows/TemplatableSparqlQueryLoader.py +192 -0
- naas_abi_core/pipeline/__init__.py +6 -0
- naas_abi_core/pipeline/pipeline.py +70 -0
- naas_abi_core/services/__init__.py +0 -0
- naas_abi_core/services/agent/Agent.py +1619 -0
- naas_abi_core/services/agent/AgentMemory_test.py +28 -0
- naas_abi_core/services/agent/Agent_test.py +214 -0
- naas_abi_core/services/agent/IntentAgent.py +1179 -0
- naas_abi_core/services/agent/IntentAgent_test.py +139 -0
- naas_abi_core/services/agent/beta/Embeddings.py +181 -0
- naas_abi_core/services/agent/beta/IntentMapper.py +120 -0
- naas_abi_core/services/agent/beta/LocalModel.py +88 -0
- naas_abi_core/services/agent/beta/VectorStore.py +89 -0
- naas_abi_core/services/agent/test_agent_memory.py +278 -0
- naas_abi_core/services/agent/test_postgres_integration.py +145 -0
- naas_abi_core/services/cache/CacheFactory.py +31 -0
- naas_abi_core/services/cache/CachePort.py +63 -0
- naas_abi_core/services/cache/CacheService.py +246 -0
- naas_abi_core/services/cache/CacheService_test.py +85 -0
- naas_abi_core/services/cache/adapters/secondary/CacheFSAdapter.py +39 -0
- naas_abi_core/services/object_storage/ObjectStorageFactory.py +57 -0
- naas_abi_core/services/object_storage/ObjectStoragePort.py +47 -0
- naas_abi_core/services/object_storage/ObjectStorageService.py +41 -0
- naas_abi_core/services/object_storage/adapters/secondary/ObjectStorageSecondaryAdapterFS.py +52 -0
- naas_abi_core/services/object_storage/adapters/secondary/ObjectStorageSecondaryAdapterNaas.py +131 -0
- naas_abi_core/services/object_storage/adapters/secondary/ObjectStorageSecondaryAdapterS3.py +171 -0
- naas_abi_core/services/ontology/OntologyPorts.py +36 -0
- naas_abi_core/services/ontology/OntologyService.py +17 -0
- naas_abi_core/services/ontology/adaptors/secondary/OntologyService_SecondaryAdaptor_NERPort.py +37 -0
- naas_abi_core/services/secret/Secret.py +138 -0
- naas_abi_core/services/secret/SecretPorts.py +45 -0
- naas_abi_core/services/secret/Secret_test.py +65 -0
- naas_abi_core/services/secret/adaptors/secondary/Base64Secret.py +57 -0
- naas_abi_core/services/secret/adaptors/secondary/Base64Secret_test.py +39 -0
- naas_abi_core/services/secret/adaptors/secondary/NaasSecret.py +88 -0
- naas_abi_core/services/secret/adaptors/secondary/NaasSecret_test.py +25 -0
- naas_abi_core/services/secret/adaptors/secondary/dotenv_secret_secondaryadaptor.py +29 -0
- naas_abi_core/services/triple_store/TripleStoreFactory.py +116 -0
- naas_abi_core/services/triple_store/TripleStorePorts.py +223 -0
- naas_abi_core/services/triple_store/TripleStoreService.py +419 -0
- naas_abi_core/services/triple_store/adaptors/secondary/AWSNeptune.py +1300 -0
- naas_abi_core/services/triple_store/adaptors/secondary/AWSNeptune_test.py +284 -0
- naas_abi_core/services/triple_store/adaptors/secondary/Oxigraph.py +597 -0
- naas_abi_core/services/triple_store/adaptors/secondary/Oxigraph_test.py +1474 -0
- naas_abi_core/services/triple_store/adaptors/secondary/TripleStoreService__SecondaryAdaptor__Filesystem.py +223 -0
- naas_abi_core/services/triple_store/adaptors/secondary/TripleStoreService__SecondaryAdaptor__ObjectStorage.py +234 -0
- naas_abi_core/services/triple_store/adaptors/secondary/base/TripleStoreService__SecondaryAdaptor__FileBase.py +18 -0
- naas_abi_core/services/vector_store/IVectorStorePort.py +101 -0
- naas_abi_core/services/vector_store/IVectorStorePort_test.py +189 -0
- naas_abi_core/services/vector_store/VectorStoreFactory.py +47 -0
- naas_abi_core/services/vector_store/VectorStoreService.py +171 -0
- naas_abi_core/services/vector_store/VectorStoreService_test.py +185 -0
- naas_abi_core/services/vector_store/__init__.py +13 -0
- naas_abi_core/services/vector_store/adapters/QdrantAdapter.py +251 -0
- naas_abi_core/services/vector_store/adapters/QdrantAdapter_test.py +57 -0
- naas_abi_core/tests/test_services_imports.py +69 -0
- naas_abi_core/utils/Expose.py +55 -0
- naas_abi_core/utils/Graph.py +182 -0
- naas_abi_core/utils/JSON.py +49 -0
- naas_abi_core/utils/LazyLoader.py +44 -0
- naas_abi_core/utils/Logger.py +12 -0
- naas_abi_core/utils/OntologyReasoner.py +141 -0
- naas_abi_core/utils/OntologyYaml.py +681 -0
- naas_abi_core/utils/SPARQL.py +256 -0
- naas_abi_core/utils/Storage.py +33 -0
- naas_abi_core/utils/StorageUtils.py +398 -0
- naas_abi_core/utils/String.py +52 -0
- naas_abi_core/utils/Workers.py +114 -0
- naas_abi_core/utils/__init__.py +0 -0
- naas_abi_core/utils/onto2py/README.md +0 -0
- naas_abi_core/utils/onto2py/__init__.py +10 -0
- naas_abi_core/utils/onto2py/__main__.py +29 -0
- naas_abi_core/utils/onto2py/onto2py.py +611 -0
- naas_abi_core/utils/onto2py/tests/ttl2py_test.py +271 -0
- naas_abi_core/workflow/__init__.py +5 -0
- naas_abi_core/workflow/workflow.py +48 -0
- naas_abi_core-1.4.1.dist-info/METADATA +630 -0
- naas_abi_core-1.4.1.dist-info/RECORD +124 -0
- naas_abi_core-1.4.1.dist-info/WHEEL +4 -0
- naas_abi_core-1.4.1.dist-info/entry_points.txt +2 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Production Dockerfile for ABI MCP Server
|
|
2
|
+
FROM python:3.13-slim
|
|
3
|
+
|
|
4
|
+
WORKDIR /app
|
|
5
|
+
|
|
6
|
+
# Install system dependencies
|
|
7
|
+
RUN apt-get update && apt-get install -y \
|
|
8
|
+
curl \
|
|
9
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
# Copy application code
|
|
13
|
+
COPY libs/naas-abi-core/naas_abi_core/apps/mcp/mcp_server.py ./
|
|
14
|
+
# COPY lib/ ./lib/
|
|
15
|
+
# COPY src/ ./src/
|
|
16
|
+
COPY .env* ./
|
|
17
|
+
|
|
18
|
+
# Copy requirements and install Python dependencies
|
|
19
|
+
COPY README.md ./
|
|
20
|
+
# COPY pyproject.toml ./
|
|
21
|
+
RUN pip install fastmcp>=2.11.2 python-dotenv
|
|
22
|
+
|
|
23
|
+
# Set environment for production MCP deployment
|
|
24
|
+
ENV MCP_TRANSPORT=sse
|
|
25
|
+
ENV ABI_API_BASE=http://api:9879
|
|
26
|
+
|
|
27
|
+
# Expose the MCP HTTP server port
|
|
28
|
+
EXPOSE 8000
|
|
29
|
+
|
|
30
|
+
# Health check
|
|
31
|
+
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
|
|
32
|
+
CMD curl -f http://localhost:8000/ || exit 1
|
|
33
|
+
|
|
34
|
+
# Run the MCP server
|
|
35
|
+
CMD ["python", "mcp_server.py"]
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ABI MCP Server - Lightweight HTTP-based Implementation
|
|
3
|
+
Exposes ABI agents as MCP tools with fast startup (no heavy imports)
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import asyncio
|
|
7
|
+
import os
|
|
8
|
+
import re
|
|
9
|
+
from typing import Any, Dict, List
|
|
10
|
+
|
|
11
|
+
import httpx
|
|
12
|
+
from dotenv import load_dotenv
|
|
13
|
+
from mcp.server.fastmcp import FastMCP
|
|
14
|
+
|
|
15
|
+
# Load environment variables from .env file if present
|
|
16
|
+
load_dotenv()
|
|
17
|
+
|
|
18
|
+
# Initialize FastMCP server
|
|
19
|
+
mcp = FastMCP("abi")
|
|
20
|
+
|
|
21
|
+
# Constants - Default to localhost for development, can be overridden by env var
|
|
22
|
+
ABI_API_BASE = os.environ.get("ABI_API_BASE", "http://localhost:9879")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def get_api_key() -> str:
|
|
26
|
+
"""Get the API key from environment variables"""
|
|
27
|
+
api_key = os.environ.get("ABI_API_KEY")
|
|
28
|
+
if not api_key:
|
|
29
|
+
print("ā ABI_API_KEY not found in environment")
|
|
30
|
+
print("š Please add it to your .env file:")
|
|
31
|
+
print(" ABI_API_KEY=your_key_here")
|
|
32
|
+
exit(1)
|
|
33
|
+
return api_key
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
async def fetch_openapi_spec() -> Dict[str, Any]:
|
|
37
|
+
"""Fetch the OpenAPI specification to discover available agents"""
|
|
38
|
+
try:
|
|
39
|
+
async with httpx.AsyncClient(timeout=10.0) as client:
|
|
40
|
+
response = await client.get(f"{ABI_API_BASE}/openapi.json")
|
|
41
|
+
response.raise_for_status()
|
|
42
|
+
return response.json()
|
|
43
|
+
except Exception as e:
|
|
44
|
+
print(f"ā Failed to fetch OpenAPI spec: {e}")
|
|
45
|
+
return {}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def extract_agents_from_openapi(openapi_spec: Dict[str, Any]) -> List[Dict[str, str]]:
|
|
49
|
+
"""Extract agent information from OpenAPI specification"""
|
|
50
|
+
agents = []
|
|
51
|
+
paths = openapi_spec.get("paths", {})
|
|
52
|
+
|
|
53
|
+
for path, methods in paths.items():
|
|
54
|
+
# Look for agent completion endpoints
|
|
55
|
+
if "/agents/" in path and path.endswith("/completion"):
|
|
56
|
+
# Extract agent name from path like /agents/{agent_name}/completion
|
|
57
|
+
agent_name = path.split("/agents/")[1].split("/completion")[0]
|
|
58
|
+
|
|
59
|
+
# Get description from POST method
|
|
60
|
+
post_method = methods.get("post", {})
|
|
61
|
+
description = post_method.get("summary", f"{agent_name} agent completion")
|
|
62
|
+
|
|
63
|
+
agents.append(
|
|
64
|
+
{
|
|
65
|
+
"name": agent_name,
|
|
66
|
+
"description": description,
|
|
67
|
+
"function_name": agent_name_to_function_name(agent_name),
|
|
68
|
+
}
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
return agents
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def agent_name_to_function_name(agent_name: str) -> str:
|
|
75
|
+
"""Convert agent name to valid Python function name"""
|
|
76
|
+
# Replace spaces and special chars with underscores, convert to lowercase
|
|
77
|
+
function_name = re.sub(r"[^a-zA-Z0-9_]", "_", agent_name.lower())
|
|
78
|
+
# Remove multiple consecutive underscores
|
|
79
|
+
function_name = re.sub(r"_+", "_", function_name)
|
|
80
|
+
# Remove leading/trailing underscores
|
|
81
|
+
function_name = function_name.strip("_")
|
|
82
|
+
# Ensure it doesn't start with a number
|
|
83
|
+
if function_name and function_name[0].isdigit():
|
|
84
|
+
function_name = f"agent_{function_name}"
|
|
85
|
+
return function_name or "unknown_agent"
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
async def call_abi_agent_http(agent_name: str, prompt: str, thread_id: int = 1) -> str:
|
|
89
|
+
"""Call ABI agents via HTTP to avoid heavy module imports"""
|
|
90
|
+
try:
|
|
91
|
+
headers = {
|
|
92
|
+
"Authorization": f"Bearer {get_api_key()}",
|
|
93
|
+
"Content-Type": "application/json",
|
|
94
|
+
"User-Agent": "ABI-MCP/1.0",
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
data = {"prompt": prompt, "thread_id": thread_id}
|
|
98
|
+
|
|
99
|
+
url = f"{ABI_API_BASE}/agents/{agent_name}/completion"
|
|
100
|
+
|
|
101
|
+
async with httpx.AsyncClient(timeout=30.0) as client:
|
|
102
|
+
response = await client.post(url, json=data, headers=headers)
|
|
103
|
+
response.raise_for_status()
|
|
104
|
+
return response.text.strip('"') # Remove JSON quotes
|
|
105
|
+
|
|
106
|
+
except httpx.ConnectError:
|
|
107
|
+
return f"ā ABI API server not running at {ABI_API_BASE}. Please start it first with: uv run api"
|
|
108
|
+
except httpx.TimeoutException:
|
|
109
|
+
return f"ā±ļø Timeout calling {agent_name} agent. The agent might be processing a complex request."
|
|
110
|
+
except httpx.HTTPStatusError as e:
|
|
111
|
+
if e.response.status_code == 401:
|
|
112
|
+
return (
|
|
113
|
+
"š Authentication failed. Check your ABI_API_KEY environment variable."
|
|
114
|
+
)
|
|
115
|
+
elif e.response.status_code == 404:
|
|
116
|
+
return f"ā Agent '{agent_name}' not found. Please check available agents via OpenAPI spec."
|
|
117
|
+
else:
|
|
118
|
+
return f"ā HTTP {e.response.status_code} error calling {agent_name} agent: {e.response.text}"
|
|
119
|
+
except Exception as e:
|
|
120
|
+
return f"ā Error calling {agent_name} agent: {str(e)}"
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def create_agent_function(agent_name: str, description: str):
|
|
124
|
+
"""Create a dynamic agent function"""
|
|
125
|
+
|
|
126
|
+
async def agent_function(prompt: str, thread_id: int = 1) -> str:
|
|
127
|
+
return await call_abi_agent_http(agent_name, prompt, thread_id)
|
|
128
|
+
|
|
129
|
+
# Set function metadata
|
|
130
|
+
agent_function.__name__ = agent_name_to_function_name(agent_name)
|
|
131
|
+
agent_function.__doc__ = f"""{description}
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
prompt: Your question or request for the {agent_name} agent
|
|
135
|
+
thread_id: Thread ID for conversation context (default: 1)
|
|
136
|
+
"""
|
|
137
|
+
|
|
138
|
+
return agent_function
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
async def wait_for_api():
|
|
142
|
+
"""Wait for the API to be available before starting"""
|
|
143
|
+
max_retries = 30 # Wait up to 5 minutes (30 * 10 seconds)
|
|
144
|
+
retry_delay = 10 # seconds
|
|
145
|
+
|
|
146
|
+
for attempt in range(max_retries):
|
|
147
|
+
try:
|
|
148
|
+
async with httpx.AsyncClient(timeout=5.0) as client:
|
|
149
|
+
response = await client.get(f"{ABI_API_BASE}")
|
|
150
|
+
if response.status_code == 200:
|
|
151
|
+
print("ā
API is ready!")
|
|
152
|
+
return True
|
|
153
|
+
except Exception:
|
|
154
|
+
pass
|
|
155
|
+
|
|
156
|
+
if attempt < max_retries - 1:
|
|
157
|
+
print(
|
|
158
|
+
f"ā³ Waiting for API to be ready... (attempt {attempt + 1}/{max_retries})"
|
|
159
|
+
)
|
|
160
|
+
await asyncio.sleep(retry_delay)
|
|
161
|
+
|
|
162
|
+
print("ā API did not become ready in time")
|
|
163
|
+
return False
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
async def register_agents_dynamically():
|
|
167
|
+
"""Discover and register agents from OpenAPI specification"""
|
|
168
|
+
print("š Discovering agents from OpenAPI specification...")
|
|
169
|
+
|
|
170
|
+
# Wait for API to be ready if not running locally
|
|
171
|
+
if not ABI_API_BASE.startswith("http://localhost"):
|
|
172
|
+
api_ready = await wait_for_api()
|
|
173
|
+
if not api_ready:
|
|
174
|
+
print("ā ļø API not ready, using fallback configuration")
|
|
175
|
+
return
|
|
176
|
+
|
|
177
|
+
openapi_spec = await fetch_openapi_spec()
|
|
178
|
+
if not openapi_spec:
|
|
179
|
+
print("ā ļø Failed to fetch OpenAPI spec, falling back to basic agents")
|
|
180
|
+
return
|
|
181
|
+
|
|
182
|
+
agents = extract_agents_from_openapi(openapi_spec)
|
|
183
|
+
|
|
184
|
+
if not agents:
|
|
185
|
+
print("ā ļø No agents found in OpenAPI spec")
|
|
186
|
+
return
|
|
187
|
+
|
|
188
|
+
print(f"š” Found {len(agents)} agents:")
|
|
189
|
+
|
|
190
|
+
for agent_info in agents:
|
|
191
|
+
agent_name = agent_info["name"]
|
|
192
|
+
description = agent_info["description"]
|
|
193
|
+
function_name = agent_info["function_name"]
|
|
194
|
+
|
|
195
|
+
print(f" ⢠{agent_name} -> {function_name}()")
|
|
196
|
+
|
|
197
|
+
# Create and register the agent function
|
|
198
|
+
agent_function = create_agent_function(agent_name, description)
|
|
199
|
+
mcp.tool()(agent_function)
|
|
200
|
+
|
|
201
|
+
print("ā
All agents registered successfully!")
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
async def setup():
|
|
205
|
+
"""Setup function to initialize the server"""
|
|
206
|
+
# Quick startup - no heavy imports!
|
|
207
|
+
print("š Starting lightweight ABI MCP Server...")
|
|
208
|
+
print(f"š” Will connect to ABI API at: {ABI_API_BASE}")
|
|
209
|
+
|
|
210
|
+
# Validate API key exists
|
|
211
|
+
get_api_key()
|
|
212
|
+
print("š API key loaded successfully")
|
|
213
|
+
|
|
214
|
+
# Dynamically discover and register agents
|
|
215
|
+
await register_agents_dynamically()
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def run():
|
|
219
|
+
"""Entry point for the script"""
|
|
220
|
+
# Run setup first
|
|
221
|
+
asyncio.run(setup())
|
|
222
|
+
|
|
223
|
+
# Determine transport type from environment
|
|
224
|
+
transport = os.environ.get("MCP_TRANSPORT", "stdio")
|
|
225
|
+
|
|
226
|
+
if transport == "sse":
|
|
227
|
+
# SSE transport for web deployment
|
|
228
|
+
print(
|
|
229
|
+
"š Starting MCP server with SSE (Server-Sent Events) transport on port 8000"
|
|
230
|
+
)
|
|
231
|
+
mcp.run(transport="sse")
|
|
232
|
+
elif transport == "http":
|
|
233
|
+
# HTTP transport using streamable-http
|
|
234
|
+
print("š Starting MCP server with streamable HTTP transport")
|
|
235
|
+
mcp.run(transport="streamable-http")
|
|
236
|
+
else:
|
|
237
|
+
# STDIO transport for local Claude Desktop integration
|
|
238
|
+
print("š Starting MCP server with STDIO transport")
|
|
239
|
+
mcp.run(transport="stdio")
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
if __name__ == "__main__":
|
|
243
|
+
run()
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Test script to validate MCP server functionality
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import asyncio
|
|
7
|
+
import httpx
|
|
8
|
+
import os
|
|
9
|
+
import sys
|
|
10
|
+
from typing import Dict, Any
|
|
11
|
+
|
|
12
|
+
# Test configuration
|
|
13
|
+
API_BASE = os.environ.get("ABI_API_BASE", "http://localhost:9879")
|
|
14
|
+
API_KEY = os.environ.get("ABI_API_KEY", "")
|
|
15
|
+
|
|
16
|
+
async def test_api_health() -> bool:
|
|
17
|
+
"""Test if the API is healthy"""
|
|
18
|
+
print(f"š Testing API health at {API_BASE}...")
|
|
19
|
+
try:
|
|
20
|
+
async with httpx.AsyncClient(timeout=5.0) as client:
|
|
21
|
+
response = await client.get(f"{API_BASE}/openapi.json")
|
|
22
|
+
if response.status_code == 200:
|
|
23
|
+
print("ā
API is healthy")
|
|
24
|
+
return True
|
|
25
|
+
else:
|
|
26
|
+
print(f"ā API returned status {response.status_code}")
|
|
27
|
+
return False
|
|
28
|
+
except Exception as e:
|
|
29
|
+
print(f"ā Failed to connect to API: {e}")
|
|
30
|
+
return False
|
|
31
|
+
|
|
32
|
+
async def test_openapi_spec() -> Dict[str, Any]:
|
|
33
|
+
"""Test if OpenAPI spec is accessible and contains agents"""
|
|
34
|
+
print(f"\nš Fetching OpenAPI spec from {API_BASE}/openapi.json...")
|
|
35
|
+
try:
|
|
36
|
+
async with httpx.AsyncClient(timeout=10.0) as client:
|
|
37
|
+
response = await client.get(f"{API_BASE}/openapi.json")
|
|
38
|
+
response.raise_for_status()
|
|
39
|
+
spec = response.json()
|
|
40
|
+
print("ā
OpenAPI spec fetched successfully")
|
|
41
|
+
return spec
|
|
42
|
+
except Exception as e:
|
|
43
|
+
print(f"ā Failed to fetch OpenAPI spec: {e}")
|
|
44
|
+
return {}
|
|
45
|
+
|
|
46
|
+
def analyze_agents(openapi_spec: Dict[str, Any]) -> list:
|
|
47
|
+
"""Extract and display agent information from OpenAPI spec"""
|
|
48
|
+
print("\nš Analyzing available agents...")
|
|
49
|
+
agents = []
|
|
50
|
+
paths = openapi_spec.get("paths", {})
|
|
51
|
+
|
|
52
|
+
for path, methods in paths.items():
|
|
53
|
+
if "/agents/" in path and path.endswith("/completion"):
|
|
54
|
+
agent_name = path.split("/agents/")[1].split("/completion")[0]
|
|
55
|
+
post_method = methods.get("post", {})
|
|
56
|
+
description = post_method.get("summary", f"{agent_name} agent")
|
|
57
|
+
agents.append({
|
|
58
|
+
"name": agent_name,
|
|
59
|
+
"description": description,
|
|
60
|
+
"endpoint": path
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
if agents:
|
|
64
|
+
print(f"ā
Found {len(agents)} agents:")
|
|
65
|
+
for agent in agents:
|
|
66
|
+
print(f" ⢠{agent['name']}: {agent['description']}")
|
|
67
|
+
else:
|
|
68
|
+
print("ā No agents found in OpenAPI spec")
|
|
69
|
+
|
|
70
|
+
return agents
|
|
71
|
+
|
|
72
|
+
async def test_agent_call(agent_name: str) -> bool:
|
|
73
|
+
"""Test calling a specific agent"""
|
|
74
|
+
if not API_KEY:
|
|
75
|
+
print("\nā ļø Skipping agent call test - ABI_API_KEY not set")
|
|
76
|
+
return False
|
|
77
|
+
|
|
78
|
+
print(f"\nš Testing agent call to '{agent_name}'...")
|
|
79
|
+
try:
|
|
80
|
+
headers = {
|
|
81
|
+
"Authorization": f"Bearer {API_KEY}",
|
|
82
|
+
"Content-Type": "application/json"
|
|
83
|
+
}
|
|
84
|
+
data = {
|
|
85
|
+
"prompt": "Hello, this is a test message. Please respond briefly.",
|
|
86
|
+
"thread_id": 1
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async with httpx.AsyncClient(timeout=30.0) as client:
|
|
90
|
+
response = await client.post(
|
|
91
|
+
f"{API_BASE}/agents/{agent_name}/completion",
|
|
92
|
+
json=data,
|
|
93
|
+
headers=headers
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
if response.status_code == 200:
|
|
97
|
+
print(f"ā
Successfully called {agent_name} agent")
|
|
98
|
+
print(f" Response: {response.text[:100]}...")
|
|
99
|
+
return True
|
|
100
|
+
else:
|
|
101
|
+
print(f"ā Agent call failed with status {response.status_code}")
|
|
102
|
+
print(f" Error: {response.text}")
|
|
103
|
+
return False
|
|
104
|
+
|
|
105
|
+
except Exception as e:
|
|
106
|
+
print(f"ā Failed to call agent: {e}")
|
|
107
|
+
return False
|
|
108
|
+
|
|
109
|
+
async def test_mcp_http_server() -> bool:
|
|
110
|
+
"""Test if MCP server is running in HTTP mode"""
|
|
111
|
+
print("\nš Testing MCP HTTP server at http://localhost:3000...")
|
|
112
|
+
try:
|
|
113
|
+
async with httpx.AsyncClient(timeout=5.0) as client:
|
|
114
|
+
response = await client.get("http://localhost:3000/")
|
|
115
|
+
if response.status_code == 200:
|
|
116
|
+
print("ā
MCP HTTP server is running")
|
|
117
|
+
return True
|
|
118
|
+
else:
|
|
119
|
+
print(f"ā MCP server returned status {response.status_code}")
|
|
120
|
+
return False
|
|
121
|
+
except Exception:
|
|
122
|
+
print("ā ļø MCP HTTP server not running (this is OK if testing STDIO mode)")
|
|
123
|
+
return False
|
|
124
|
+
|
|
125
|
+
async def main():
|
|
126
|
+
"""Run all tests"""
|
|
127
|
+
print("š MCP Server Validation Tests")
|
|
128
|
+
print("=" * 50)
|
|
129
|
+
|
|
130
|
+
# Test 1: API Health
|
|
131
|
+
api_healthy = await test_api_health()
|
|
132
|
+
if not api_healthy:
|
|
133
|
+
print("\nā ļø API is not running. Please start it with: uv run api")
|
|
134
|
+
sys.exit(1)
|
|
135
|
+
|
|
136
|
+
# Test 2: OpenAPI Spec
|
|
137
|
+
openapi_spec = await test_openapi_spec()
|
|
138
|
+
if not openapi_spec:
|
|
139
|
+
sys.exit(1)
|
|
140
|
+
|
|
141
|
+
# Test 3: Analyze Agents
|
|
142
|
+
agents = analyze_agents(openapi_spec)
|
|
143
|
+
if not agents:
|
|
144
|
+
print("\nā ļø No agents found. Check your API configuration.")
|
|
145
|
+
sys.exit(1)
|
|
146
|
+
|
|
147
|
+
# Test 4: Test Agent Call (if API key is set)
|
|
148
|
+
if agents:
|
|
149
|
+
# Test the first available agent
|
|
150
|
+
await test_agent_call(agents[0]["name"])
|
|
151
|
+
|
|
152
|
+
# Test 5: MCP HTTP Server (optional)
|
|
153
|
+
await test_mcp_http_server()
|
|
154
|
+
|
|
155
|
+
print("\n" + "=" * 50)
|
|
156
|
+
print("ā
Validation complete!")
|
|
157
|
+
print("\nNext steps:")
|
|
158
|
+
print("1. Start MCP server locally: python mcp_server.py")
|
|
159
|
+
print("2. For HTTP mode: MCP_TRANSPORT=http python mcp_server.py")
|
|
160
|
+
print("3. For Claude Desktop integration, add to MCP settings")
|
|
161
|
+
|
|
162
|
+
if __name__ == "__main__":
|
|
163
|
+
asyncio.run(main())
|