open-metadata--openmetadata
bf2343b7e4
Integration Tests - MySQL + Elasticsearch / Detect Changes (push) Has been cancelled
Integration Tests - MySQL + Elasticsearch / integration-tests-mysql-elasticsearch (push) Has been cancelled
Integration Tests - PostgreSQL + Elasticsearch + Redis / Detect Changes (push) Has been cancelled
Integration Tests - PostgreSQL + Elasticsearch + Redis / integration-tests-postgres-elasticsearch-redis (push) Has been cancelled
Integration Tests - PostgreSQL + OpenSearch / Detect Changes (push) Has been cancelled
Integration Tests - PostgreSQL + OpenSearch / integration-tests-postgres-opensearch (push) Has been cancelled
Java Checkstyle / java-checkstyle (push) Has been cancelled
Maven Collate Tests / maven-collate-ci (push) Has been cancelled
OpenMetadata Service Unit Tests / openmetadata-service-unit-tests-status (push) Has been cancelled
Publish Package to Maven Central Repository / publish-maven-packages (push) Has been cancelled
OpenMetadata Service Unit Tests / Detect Changes (push) Has been cancelled
OpenMetadata Service Unit Tests / openmetadata-service-unit-tests (push) Has been cancelled
OpenMetadata Service Unit Tests / k8s_operator-unit-tests (push) Has been cancelled
373 行
12 KiB
Python
373 行
12 KiB
Python
# Copyright 2025 Collate
|
|
# Licensed under the Collate Community License, Version 1.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
# https://github.com/open-metadata/OpenMetadata/blob/main/ingestion/LICENSE
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
"""
|
|
MCP Integration Tests
|
|
|
|
Tests for MCP service creation and ingestion workflow.
|
|
Requires:
|
|
- Running OpenMetadata server (localhost:8585)
|
|
- Optionally a running MCP server for full E2E tests
|
|
"""
|
|
|
|
import json
|
|
import tempfile
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
import pytest
|
|
|
|
from _openmetadata_testutils.ometa import int_admin_ometa
|
|
from metadata.generated.schema.api.services.createMcpService import (
|
|
CreateMcpServiceRequest,
|
|
)
|
|
from metadata.generated.schema.entity.services.connections.mcp.mcpConnection import (
|
|
DiscoveryMethod,
|
|
McpConnection,
|
|
McpServerConfig,
|
|
McpType,
|
|
TransportType,
|
|
)
|
|
from metadata.generated.schema.entity.services.mcpService import (
|
|
McpConnection as McpServiceConnection,
|
|
)
|
|
from metadata.generated.schema.entity.services.mcpService import (
|
|
McpService,
|
|
McpServiceType,
|
|
)
|
|
from metadata.generated.schema.type.basic import EntityName
|
|
from metadata.ingestion.source.mcp.connection import McpConnectionManager
|
|
|
|
|
|
class TestMcpServiceAPI:
|
|
"""Tests for MCP Service API operations"""
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def setup(self):
|
|
self.metadata = int_admin_ometa()
|
|
self.service_name = "test-mcp-service"
|
|
yield
|
|
self._cleanup()
|
|
|
|
def _cleanup(self):
|
|
try:
|
|
service = self.metadata.get_by_name(
|
|
entity=McpService,
|
|
fqn=self.service_name,
|
|
)
|
|
if service:
|
|
self.metadata.delete(
|
|
entity=McpService,
|
|
entity_id=service.id,
|
|
recursive=True,
|
|
hard_delete=True,
|
|
)
|
|
except Exception:
|
|
pass
|
|
|
|
def test_create_mcp_service(self):
|
|
"""Test creating an MCP service via the API"""
|
|
service_request = CreateMcpServiceRequest(
|
|
name=EntityName(self.service_name),
|
|
serviceType=McpServiceType.Mcp,
|
|
connection=McpServiceConnection(
|
|
config=McpConnection(
|
|
type=McpType.Mcp,
|
|
discoveryMethod=DiscoveryMethod.DirectConnection,
|
|
servers=[
|
|
McpServerConfig(
|
|
name="test-server",
|
|
transport=TransportType.Stdio,
|
|
command="echo",
|
|
args=["test"],
|
|
),
|
|
],
|
|
)
|
|
),
|
|
)
|
|
|
|
created_service = self.metadata.create_or_update(service_request)
|
|
|
|
assert created_service is not None
|
|
assert created_service.name.root == self.service_name
|
|
assert created_service.serviceType == McpServiceType.Mcp
|
|
|
|
def test_get_mcp_service_by_name(self):
|
|
"""Test retrieving an MCP service by name"""
|
|
service_request = CreateMcpServiceRequest(
|
|
name=EntityName(self.service_name),
|
|
serviceType=McpServiceType.Mcp,
|
|
connection=McpServiceConnection(
|
|
config=McpConnection(
|
|
type=McpType.Mcp,
|
|
discoveryMethod=DiscoveryMethod.DirectConnection,
|
|
servers=[
|
|
McpServerConfig(
|
|
name="test-server",
|
|
transport=TransportType.SSE,
|
|
url="http://localhost:8080",
|
|
),
|
|
],
|
|
)
|
|
),
|
|
)
|
|
|
|
self.metadata.create_or_update(service_request)
|
|
|
|
retrieved_service = self.metadata.get_by_name(
|
|
entity=McpService,
|
|
fqn=self.service_name,
|
|
)
|
|
|
|
assert retrieved_service is not None
|
|
assert retrieved_service.name.root == self.service_name
|
|
|
|
def test_delete_mcp_service(self):
|
|
"""Test deleting an MCP service"""
|
|
service_request = CreateMcpServiceRequest(
|
|
name=EntityName(self.service_name),
|
|
serviceType=McpServiceType.Mcp,
|
|
connection=McpServiceConnection(
|
|
config=McpConnection(
|
|
type=McpType.Mcp,
|
|
discoveryMethod=DiscoveryMethod.DirectConnection,
|
|
servers=[],
|
|
)
|
|
),
|
|
)
|
|
|
|
created_service = self.metadata.create_or_update(service_request)
|
|
|
|
self.metadata.delete(
|
|
entity=McpService,
|
|
entity_id=created_service.id,
|
|
hard_delete=True,
|
|
)
|
|
|
|
deleted_service = self.metadata.get_by_name(
|
|
entity=McpService,
|
|
fqn=self.service_name,
|
|
)
|
|
|
|
assert deleted_service is None
|
|
|
|
|
|
class TestMcpConnectionManagerIntegration:
|
|
"""Tests for MCP Connection Manager"""
|
|
|
|
def test_discover_from_direct_config(self):
|
|
"""Test discovering servers from direct configuration"""
|
|
connection = McpConnection(
|
|
type=McpType.Mcp,
|
|
discoveryMethod=DiscoveryMethod.DirectConnection,
|
|
servers=[
|
|
McpServerConfig(
|
|
name="server-1",
|
|
transport=TransportType.Stdio,
|
|
command="npx",
|
|
args=["-y", "@modelcontextprotocol/server-memory"],
|
|
),
|
|
McpServerConfig(
|
|
name="server-2",
|
|
transport=TransportType.SSE,
|
|
url="http://localhost:8080",
|
|
),
|
|
],
|
|
)
|
|
|
|
manager = McpConnectionManager(connection)
|
|
servers = manager.discover_servers()
|
|
|
|
assert len(servers) == 2
|
|
assert servers[0].name == "server-1"
|
|
assert servers[0].transport == "Stdio"
|
|
assert servers[0].command == "npx"
|
|
assert servers[1].name == "server-2"
|
|
assert servers[1].transport == "SSE"
|
|
assert servers[1].url == "http://localhost:8080"
|
|
|
|
def test_discover_from_config_file(self):
|
|
"""Test discovering servers from config file"""
|
|
config_content = {
|
|
"mcpServers": {
|
|
"filesystem": {
|
|
"command": "npx",
|
|
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"],
|
|
},
|
|
"memory": {
|
|
"command": "uvx",
|
|
"args": ["mcp-server-memory"],
|
|
},
|
|
}
|
|
}
|
|
|
|
with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as f:
|
|
json.dump(config_content, f)
|
|
config_path = f.name
|
|
|
|
connection = McpConnection(
|
|
type=McpType.Mcp,
|
|
discoveryMethod=DiscoveryMethod.ConfigFile,
|
|
configFilePaths=[config_path],
|
|
)
|
|
|
|
manager = McpConnectionManager(connection)
|
|
servers = manager.discover_servers()
|
|
|
|
assert len(servers) == 2
|
|
server_names = {s.name for s in servers}
|
|
assert "filesystem" in server_names
|
|
assert "memory" in server_names
|
|
|
|
|
|
class TestMcpIngestionWorkflow:
|
|
"""Tests for MCP ingestion workflow execution"""
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def setup(self):
|
|
self.metadata = int_admin_ometa()
|
|
self.service_name = "test-mcp-ingestion"
|
|
yield
|
|
self._cleanup()
|
|
|
|
def _cleanup(self):
|
|
try:
|
|
service = self.metadata.get_by_name(
|
|
entity=McpService,
|
|
fqn=self.service_name,
|
|
)
|
|
if service:
|
|
self.metadata.delete(
|
|
entity=McpService,
|
|
entity_id=service.id,
|
|
recursive=True,
|
|
hard_delete=True,
|
|
)
|
|
except Exception:
|
|
pass
|
|
|
|
@patch("metadata.ingestion.source.mcp.metadata.McpClient")
|
|
def test_ingestion_workflow_with_mock_server(self, mock_client_class):
|
|
"""Test running the MCP ingestion workflow with a mocked server"""
|
|
mock_client = MagicMock()
|
|
mock_client.server_info = {"name": "mock-server", "version": "1.0"}
|
|
mock_client.capabilities = {"tools": {}}
|
|
mock_client_class.return_value = mock_client
|
|
|
|
service_request = CreateMcpServiceRequest(
|
|
name=EntityName(self.service_name),
|
|
serviceType=McpServiceType.Mcp,
|
|
connection=McpServiceConnection(
|
|
config=McpConnection(
|
|
type=McpType.Mcp,
|
|
discoveryMethod=DiscoveryMethod.DirectConnection,
|
|
servers=[
|
|
McpServerConfig(
|
|
name="mock-server",
|
|
transport=TransportType.Stdio,
|
|
command="echo",
|
|
args=["test"],
|
|
),
|
|
],
|
|
fetchTools=True,
|
|
fetchResources=True,
|
|
fetchPrompts=True,
|
|
)
|
|
),
|
|
)
|
|
|
|
created_service = self.metadata.create_or_update(service_request)
|
|
assert created_service is not None
|
|
assert created_service.name.root == self.service_name
|
|
|
|
|
|
@pytest.mark.skip(reason="Requires running MCP server")
|
|
class TestMcpE2EWithRealServer:
|
|
"""
|
|
End-to-end tests with a real MCP server.
|
|
|
|
To run these tests:
|
|
1. Start an MCP server (e.g., npx -y @modelcontextprotocol/server-memory)
|
|
2. Run with: pytest -v tests/integration/mcp/test_mcp_integration.py::TestMcpE2EWithRealServer
|
|
"""
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def setup(self):
|
|
self.metadata = int_admin_ometa()
|
|
self.service_name = "test-mcp-e2e"
|
|
yield
|
|
self._cleanup()
|
|
|
|
def _cleanup(self):
|
|
try:
|
|
service = self.metadata.get_by_name(
|
|
entity=McpService,
|
|
fqn=self.service_name,
|
|
)
|
|
if service:
|
|
self.metadata.delete(
|
|
entity=McpService,
|
|
entity_id=service.id,
|
|
recursive=True,
|
|
hard_delete=True,
|
|
)
|
|
except Exception:
|
|
pass
|
|
|
|
def test_e2e_with_memory_server(self):
|
|
"""
|
|
Test full E2E flow with the MCP memory server.
|
|
|
|
Requires: npx -y @modelcontextprotocol/server-memory
|
|
"""
|
|
service_request = CreateMcpServiceRequest(
|
|
name=EntityName(self.service_name),
|
|
serviceType=McpServiceType.Mcp,
|
|
connection=McpServiceConnection(
|
|
config=McpConnection(
|
|
type=McpType.Mcp,
|
|
discoveryMethod=DiscoveryMethod.DirectConnection,
|
|
servers=[
|
|
McpServerConfig(
|
|
name="memory-server",
|
|
transport=TransportType.Stdio,
|
|
command="npx",
|
|
args=["-y", "@modelcontextprotocol/server-memory"],
|
|
),
|
|
],
|
|
fetchTools=True,
|
|
fetchResources=True,
|
|
fetchPrompts=True,
|
|
)
|
|
),
|
|
)
|
|
|
|
created_service = self.metadata.create_or_update(service_request)
|
|
assert created_service is not None
|
|
|
|
connection = McpConnection(
|
|
type=McpType.Mcp,
|
|
discoveryMethod=DiscoveryMethod.DirectConnection,
|
|
servers=[
|
|
McpServerConfig(
|
|
name="memory-server",
|
|
transport=TransportType.Stdio,
|
|
command="npx",
|
|
args=["-y", "@modelcontextprotocol/server-memory"],
|
|
),
|
|
],
|
|
)
|
|
|
|
manager = McpConnectionManager(connection)
|
|
servers = manager.discover_servers()
|
|
assert len(servers) == 1
|
|
|
|
success = manager.test_server_connection(servers[0])
|
|
assert success is True
|