|
16 | 16 | from cycode.logger import LoggersManager, get_logger |
17 | 17 |
|
18 | 18 | try: |
19 | | - from fastmcp import FastMCP |
20 | | - from fastmcp.tools import Tool |
| 19 | + from mcp.server.fastmcp import FastMCP |
| 20 | + from mcp.server.fastmcp.tools import Tool |
21 | 21 | except ImportError: |
22 | 22 | raise ImportError( |
23 | 23 | 'Cycode MCP is not supported for your Python version. MCP support requires Python 3.10 or higher.' |
@@ -404,74 +404,30 @@ async def cycode_status() -> str: |
404 | 404 | return json.dumps({'error': f'Status check failed: {e!s}'}, indent=2) |
405 | 405 |
|
406 | 406 |
|
407 | | -_SCAN_TOOLS = [cycode_status, cycode_secret_scan, cycode_sca_scan, cycode_iac_scan, cycode_sast_scan] |
408 | | - |
409 | | - |
410 | | -def _create_scan_only_mcp_server(host: str, port: int) -> FastMCP: |
411 | | - """Create MCP server with scan tools only (fallback when API spec unavailable).""" |
412 | | - tools = [Tool.from_function(fn) for fn in _SCAN_TOOLS] |
413 | | - _logger.info('Creating MCP server with scan tools only: %s', [t.name for t in tools]) |
| 407 | +def _create_mcp_server(host: str, port: int) -> FastMCP: |
| 408 | + """Create and configure the MCP server.""" |
| 409 | + tools = [ |
| 410 | + Tool.from_function(cycode_status), |
| 411 | + Tool.from_function(cycode_secret_scan), |
| 412 | + Tool.from_function(cycode_sca_scan), |
| 413 | + Tool.from_function(cycode_iac_scan), |
| 414 | + Tool.from_function(cycode_sast_scan), |
| 415 | + ] |
| 416 | + _logger.info('Creating MCP server with tools: %s', [tool.name for tool in tools]) |
414 | 417 | return FastMCP( |
415 | 418 | 'cycode', |
416 | 419 | tools=tools, |
| 420 | + host=host, |
| 421 | + port=port, |
| 422 | + debug=_is_debug_mode(), |
| 423 | + log_level='DEBUG' if _is_debug_mode() else 'INFO', |
417 | 424 | ) |
418 | 425 |
|
419 | 426 |
|
420 | | -async def _create_mcp_server_with_api(host: str, port: int) -> FastMCP: |
421 | | - """Create MCP server with both API v4 tools and scan tools.""" |
422 | | - from cycode.cli.apps.api.async_auth_client import AsyncTokenManager, CycodeAsyncAuthClient |
423 | | - from cycode.cli.apps.api.openapi_spec import ( |
424 | | - disable_output_validation, |
425 | | - filter_openapi_get_only, |
426 | | - get_openapi_spec, |
427 | | - normalize_tool_names, |
428 | | - ) |
429 | | - |
430 | | - # Load and filter OpenAPI spec |
431 | | - spec = get_openapi_spec() |
432 | | - spec = filter_openapi_get_only(spec) |
433 | | - spec = normalize_tool_names(spec) |
434 | | - |
435 | | - # Create async auth client |
436 | | - token_manager = AsyncTokenManager() |
437 | | - client = CycodeAsyncAuthClient(token_manager) |
438 | | - |
439 | | - # Count endpoints for logging |
440 | | - endpoint_count = sum(len(methods) for methods in spec.get('paths', {}).values()) |
441 | | - _logger.info('Creating MCP server with %d API endpoints + scan tools', endpoint_count) |
442 | | - |
443 | | - # Create server from OpenAPI (API v4 tools - experimental) |
444 | | - server = FastMCP.from_openapi( |
445 | | - openapi_spec=spec, |
446 | | - client=client, |
447 | | - name='cycode', |
448 | | - mcp_component_fn=disable_output_validation, |
449 | | - instructions='Cycode API v4 tools are experimental and may change.', |
450 | | - ) |
451 | | - |
452 | | - # Add scan tools on top |
453 | | - for fn in _SCAN_TOOLS: |
454 | | - server.add_tool(Tool.from_function(fn)) |
455 | | - |
456 | | - return server |
457 | | - |
458 | | - |
459 | 427 | def _run_mcp_server(transport: McpTransportOption, host: str, port: int) -> None: |
460 | 428 | """Run the MCP server using transport.""" |
461 | | - try: |
462 | | - # Try to create server with API v4 tools |
463 | | - server = asyncio.run(_create_mcp_server_with_api(host, port)) |
464 | | - _logger.info('MCP server created with API v4 tools and scan tools') |
465 | | - except Exception as e: |
466 | | - # Fall back to scan-only server if API spec is unavailable |
467 | | - _logger.warning('Could not load API v4 tools, falling back to scan-only: %s', e) |
468 | | - server = _create_scan_only_mcp_server(host, port) |
469 | | - |
470 | | - run_kwargs = {'transport': str(transport)} |
471 | | - if str(transport) != 'stdio': |
472 | | - run_kwargs['host'] = host |
473 | | - run_kwargs['port'] = port |
474 | | - server.run(**run_kwargs) # type: ignore[arg-type] |
| 429 | + mcp = _create_mcp_server(host, port) |
| 430 | + mcp.run(transport=str(transport)) # type: ignore[arg-type] |
475 | 431 |
|
476 | 432 |
|
477 | 433 | def mcp_command( |
@@ -499,19 +455,13 @@ def mcp_command( |
499 | 455 | ) -> None: |
500 | 456 | """:robot: Start the Cycode MCP (Model Context Protocol) server. |
501 | 457 |
|
502 | | - The MCP server provides tools for: |
503 | | -
|
504 | | - **Scanning:** |
| 458 | + The MCP server provides tools for scanning code with Cycode CLI: |
505 | 459 | - cycode_secret_scan: Scan for hardcoded secrets |
506 | 460 | - cycode_sca_scan: Software Composition Analysis scanning |
507 | 461 | - cycode_iac_scan: Infrastructure as Code scanning |
508 | 462 | - cycode_sast_scan: Static Application Security Testing scanning |
509 | 463 | - cycode_status: Get Cycode CLI status (version, auth status) and configuration |
510 | 464 |
|
511 | | - **Cycode API v4** (when authenticated): |
512 | | - - 100+ read-only tools auto-generated from the Cycode API |
513 | | - - Projects, compliance, members, scans, workflows, and more |
514 | | -
|
515 | 465 | Examples: |
516 | 466 | cycode mcp # Start with default transport (stdio) |
517 | 467 | cycode mcp -t sse -p 8080 # Start with Server-Sent Events (SSE) transport on port 8080 |
|
0 commit comments