From 0fc40d0fdae402168b74721773ebdf8dff6bb3ae Mon Sep 17 00:00:00 2001 From: lmiranda Date: Mon, 26 Jan 2026 14:32:10 -0500 Subject: [PATCH] feat(contract-validator): create plugin structure (#185) Create the basic plugin structure for contract-validator: Plugin structure: - plugins/contract-validator/.claude-plugin/plugin.json - plugins/contract-validator/.mcp.json - plugins/contract-validator/mcp-servers/contract-validator -> symlink MCP server scaffolding: - mcp-servers/contract-validator/mcp_server/server.py (7 placeholder tools) - mcp-servers/contract-validator/pyproject.toml - mcp-servers/contract-validator/requirements.txt - Virtual environment with mcp>=0.9.0 Tools defined (placeholders): - parse_plugin_interface - parse_claude_md_agents - validate_compatibility - validate_agent_refs - validate_data_flow - generate_compatibility_report - list_issues Sprint: Sprint 2 - contract-validator Plugin Co-Authored-By: Claude Opus 4.5 --- .../contract-validator/mcp_server/__init__.py | 3 + .../contract-validator/mcp_server/server.py | 297 ++++++++++++++++++ mcp-servers/contract-validator/pyproject.toml | 41 +++ .../contract-validator/requirements.txt | 9 + .../.claude-plugin/plugin.json | 22 ++ plugins/contract-validator/.mcp.json | 10 + .../mcp-servers/contract-validator | 1 + 7 files changed, 383 insertions(+) create mode 100644 mcp-servers/contract-validator/mcp_server/__init__.py create mode 100644 mcp-servers/contract-validator/mcp_server/server.py create mode 100644 mcp-servers/contract-validator/pyproject.toml create mode 100644 mcp-servers/contract-validator/requirements.txt create mode 100644 plugins/contract-validator/.claude-plugin/plugin.json create mode 100644 plugins/contract-validator/.mcp.json create mode 120000 plugins/contract-validator/mcp-servers/contract-validator diff --git a/mcp-servers/contract-validator/mcp_server/__init__.py b/mcp-servers/contract-validator/mcp_server/__init__.py new file mode 100644 index 0000000..0e351ba --- /dev/null +++ b/mcp-servers/contract-validator/mcp_server/__init__.py @@ -0,0 +1,3 @@ +"""Contract Validator MCP Server - Cross-plugin compatibility validation.""" + +__version__ = "1.0.0" diff --git a/mcp-servers/contract-validator/mcp_server/server.py b/mcp-servers/contract-validator/mcp_server/server.py new file mode 100644 index 0000000..779a304 --- /dev/null +++ b/mcp-servers/contract-validator/mcp_server/server.py @@ -0,0 +1,297 @@ +""" +MCP Server entry point for Contract Validator. + +Provides cross-plugin compatibility validation and Claude.md agent verification +tools to Claude Code via JSON-RPC 2.0 over stdio. +""" +import asyncio +import logging +import json +from mcp.server import Server +from mcp.server.stdio import stdio_server +from mcp.types import Tool, TextContent + +# Suppress noisy MCP validation warnings on stderr +logging.basicConfig(level=logging.INFO) +logging.getLogger("root").setLevel(logging.ERROR) +logging.getLogger("mcp").setLevel(logging.ERROR) +logger = logging.getLogger(__name__) + + +class ContractValidatorMCPServer: + """MCP Server for cross-plugin compatibility validation""" + + def __init__(self): + self.server = Server("contract-validator-mcp") + + async def initialize(self): + """Initialize server.""" + logger.info("Contract Validator MCP Server initialized") + + def setup_tools(self): + """Register all available tools with the MCP server""" + + @self.server.list_tools() + async def list_tools() -> list[Tool]: + """Return list of available tools""" + tools = [ + # Parse tools (to be implemented in #186) + Tool( + name="parse_plugin_interface", + description="Parse plugin README.md to extract interface declarations (inputs, outputs, tools)", + inputSchema={ + "type": "object", + "properties": { + "plugin_path": { + "type": "string", + "description": "Path to plugin directory or README.md" + } + }, + "required": ["plugin_path"] + } + ), + Tool( + name="parse_claude_md_agents", + description="Parse Claude.md to extract agent definitions and their tool sequences", + inputSchema={ + "type": "object", + "properties": { + "claude_md_path": { + "type": "string", + "description": "Path to CLAUDE.md file" + } + }, + "required": ["claude_md_path"] + } + ), + # Validation tools (to be implemented in #187) + Tool( + name="validate_compatibility", + description="Validate compatibility between two plugin interfaces", + inputSchema={ + "type": "object", + "properties": { + "plugin_a": { + "type": "string", + "description": "Path to first plugin" + }, + "plugin_b": { + "type": "string", + "description": "Path to second plugin" + } + }, + "required": ["plugin_a", "plugin_b"] + } + ), + Tool( + name="validate_agent_refs", + description="Validate that all tool references in an agent definition exist", + inputSchema={ + "type": "object", + "properties": { + "agent_name": { + "type": "string", + "description": "Name of agent to validate" + }, + "claude_md_path": { + "type": "string", + "description": "Path to CLAUDE.md containing agent" + }, + "plugin_paths": { + "type": "array", + "items": {"type": "string"}, + "description": "Paths to available plugins" + } + }, + "required": ["agent_name", "claude_md_path"] + } + ), + Tool( + name="validate_data_flow", + description="Validate data flow through an agent's tool sequence", + inputSchema={ + "type": "object", + "properties": { + "agent_name": { + "type": "string", + "description": "Name of agent to validate" + }, + "claude_md_path": { + "type": "string", + "description": "Path to CLAUDE.md containing agent" + } + }, + "required": ["agent_name", "claude_md_path"] + } + ), + # Report tools (to be implemented in #188) + Tool( + name="generate_compatibility_report", + description="Generate a comprehensive compatibility report for all plugins", + inputSchema={ + "type": "object", + "properties": { + "marketplace_path": { + "type": "string", + "description": "Path to marketplace root directory" + }, + "format": { + "type": "string", + "enum": ["markdown", "json"], + "default": "markdown", + "description": "Output format" + } + }, + "required": ["marketplace_path"] + } + ), + Tool( + name="list_issues", + description="List validation issues with optional filtering", + inputSchema={ + "type": "object", + "properties": { + "marketplace_path": { + "type": "string", + "description": "Path to marketplace root directory" + }, + "severity": { + "type": "string", + "enum": ["error", "warning", "info", "all"], + "default": "all", + "description": "Filter by severity" + }, + "issue_type": { + "type": "string", + "enum": ["missing_tool", "interface_mismatch", "optional_dependency", "undeclared_output", "all"], + "default": "all", + "description": "Filter by issue type" + } + }, + "required": ["marketplace_path"] + } + ) + ] + return tools + + @self.server.call_tool() + async def call_tool(name: str, arguments: dict) -> list[TextContent]: + """Handle tool invocation.""" + try: + # All tools return placeholder responses for now + # Actual implementation will be added in issues #186, #187, #188 + + if name == "parse_plugin_interface": + result = await self._parse_plugin_interface(**arguments) + elif name == "parse_claude_md_agents": + result = await self._parse_claude_md_agents(**arguments) + elif name == "validate_compatibility": + result = await self._validate_compatibility(**arguments) + elif name == "validate_agent_refs": + result = await self._validate_agent_refs(**arguments) + elif name == "validate_data_flow": + result = await self._validate_data_flow(**arguments) + elif name == "generate_compatibility_report": + result = await self._generate_compatibility_report(**arguments) + elif name == "list_issues": + result = await self._list_issues(**arguments) + else: + raise ValueError(f"Unknown tool: {name}") + + return [TextContent( + type="text", + text=json.dumps(result, indent=2, default=str) + )] + + except Exception as e: + logger.error(f"Tool {name} failed: {e}") + return [TextContent( + type="text", + text=json.dumps({"error": str(e)}, indent=2) + )] + + # Placeholder implementations - to be completed in subsequent issues + + async def _parse_plugin_interface(self, plugin_path: str) -> dict: + """Parse plugin interface from README.md (placeholder)""" + return { + "status": "not_implemented", + "message": "Implementation pending - Issue #186", + "plugin_path": plugin_path + } + + async def _parse_claude_md_agents(self, claude_md_path: str) -> dict: + """Parse agents from CLAUDE.md (placeholder)""" + return { + "status": "not_implemented", + "message": "Implementation pending - Issue #186", + "claude_md_path": claude_md_path + } + + async def _validate_compatibility(self, plugin_a: str, plugin_b: str) -> dict: + """Validate compatibility between plugins (placeholder)""" + return { + "status": "not_implemented", + "message": "Implementation pending - Issue #187", + "plugin_a": plugin_a, + "plugin_b": plugin_b + } + + async def _validate_agent_refs(self, agent_name: str, claude_md_path: str, plugin_paths: list = None) -> dict: + """Validate agent tool references (placeholder)""" + return { + "status": "not_implemented", + "message": "Implementation pending - Issue #187", + "agent_name": agent_name, + "claude_md_path": claude_md_path + } + + async def _validate_data_flow(self, agent_name: str, claude_md_path: str) -> dict: + """Validate agent data flow (placeholder)""" + return { + "status": "not_implemented", + "message": "Implementation pending - Issue #187", + "agent_name": agent_name, + "claude_md_path": claude_md_path + } + + async def _generate_compatibility_report(self, marketplace_path: str, format: str = "markdown") -> dict: + """Generate compatibility report (placeholder)""" + return { + "status": "not_implemented", + "message": "Implementation pending - Issue #188", + "marketplace_path": marketplace_path, + "format": format + } + + async def _list_issues(self, marketplace_path: str, severity: str = "all", issue_type: str = "all") -> dict: + """List validation issues (placeholder)""" + return { + "status": "not_implemented", + "message": "Implementation pending - Issue #188", + "marketplace_path": marketplace_path, + "severity": severity, + "issue_type": issue_type + } + + async def run(self): + """Run the MCP server""" + await self.initialize() + self.setup_tools() + + async with stdio_server() as (read_stream, write_stream): + await self.server.run( + read_stream, + write_stream, + self.server.create_initialization_options() + ) + + +async def main(): + """Main entry point""" + server = ContractValidatorMCPServer() + await server.run() + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/mcp-servers/contract-validator/pyproject.toml b/mcp-servers/contract-validator/pyproject.toml new file mode 100644 index 0000000..460a1eb --- /dev/null +++ b/mcp-servers/contract-validator/pyproject.toml @@ -0,0 +1,41 @@ +[build-system] +requires = ["setuptools>=61.0", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "contract-validator-mcp" +version = "1.0.0" +description = "MCP Server for cross-plugin compatibility validation and agent verification" +readme = "README.md" +license = {text = "MIT"} +requires-python = ">=3.10" +authors = [ + {name = "Leo Miranda"} +] +classifiers = [ + "Development Status :: 4 - Beta", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", +] +dependencies = [ + "mcp>=0.9.0", + "pydantic>=2.5.0", +] + +[project.optional-dependencies] +dev = [ + "pytest>=7.4.3", + "pytest-asyncio>=0.23.0", +] + +[tool.setuptools.packages.find] +where = ["."] +include = ["mcp_server*"] + +[tool.pytest.ini_options] +asyncio_mode = "auto" +testpaths = ["tests"] diff --git a/mcp-servers/contract-validator/requirements.txt b/mcp-servers/contract-validator/requirements.txt new file mode 100644 index 0000000..05b923d --- /dev/null +++ b/mcp-servers/contract-validator/requirements.txt @@ -0,0 +1,9 @@ +# MCP SDK +mcp>=0.9.0 + +# Utilities +pydantic>=2.5.0 + +# Testing +pytest>=7.4.3 +pytest-asyncio>=0.23.0 diff --git a/plugins/contract-validator/.claude-plugin/plugin.json b/plugins/contract-validator/.claude-plugin/plugin.json new file mode 100644 index 0000000..394de21 --- /dev/null +++ b/plugins/contract-validator/.claude-plugin/plugin.json @@ -0,0 +1,22 @@ +{ + "name": "contract-validator", + "version": "1.0.0", + "description": "Cross-plugin compatibility validation and Claude.md agent verification", + "author": { + "name": "Leo Miranda", + "email": "leobmiranda@gmail.com" + }, + "homepage": "https://gitea.hotserv.cloud/personal-projects/leo-claude-mktplace/src/branch/main/plugins/contract-validator/README.md", + "repository": "https://gitea.hotserv.cloud/personal-projects/leo-claude-mktplace.git", + "license": "MIT", + "keywords": [ + "validation", + "contracts", + "compatibility", + "agents", + "interfaces", + "cross-plugin" + ], + "commands": ["./commands/"], + "mcpServers": ["./.mcp.json"] +} diff --git a/plugins/contract-validator/.mcp.json b/plugins/contract-validator/.mcp.json new file mode 100644 index 0000000..f3aa57b --- /dev/null +++ b/plugins/contract-validator/.mcp.json @@ -0,0 +1,10 @@ +{ + "mcpServers": { + "contract-validator": { + "type": "stdio", + "command": "${CLAUDE_PLUGIN_ROOT}/mcp-servers/contract-validator/.venv/bin/python", + "args": ["-m", "mcp_server.server"], + "cwd": "${CLAUDE_PLUGIN_ROOT}/mcp-servers/contract-validator" + } + } +} diff --git a/plugins/contract-validator/mcp-servers/contract-validator b/plugins/contract-validator/mcp-servers/contract-validator new file mode 120000 index 0000000..742406b --- /dev/null +++ b/plugins/contract-validator/mcp-servers/contract-validator @@ -0,0 +1 @@ +../../../mcp-servers/contract-validator \ No newline at end of file