|
1 | 1 | # stdlib imports
|
2 |
| -from pathlib import Path |
3 | 2 | import json
|
| 3 | +from pathlib import Path |
4 | 4 |
|
5 | 5 | # third party imports
|
6 | 6 | import pytest
|
@@ -587,3 +587,159 @@ def test_input_definition_with_yaml_file(tmp_path: Path):
|
587 | 587 | assert isinstance(server, StdioServerConfig)
|
588 | 588 | assert server.command == "python -m test_module"
|
589 | 589 | assert server.args == ["--config", "/etc/config.json"]
|
| 590 | + |
| 591 | + |
| 592 | +def test_jsonc_comment_stripping(): |
| 593 | + """Test stripping of // comments from JSONC content.""" |
| 594 | + # Test basic comment stripping |
| 595 | + content_with_comments = """ |
| 596 | +{ |
| 597 | + // This is a comment |
| 598 | + "servers": { |
| 599 | + "test_server": { |
| 600 | + "type": "stdio", |
| 601 | + "command": "python test.py" // End of line comment |
| 602 | + } |
| 603 | + }, |
| 604 | + // Another comment |
| 605 | + "inputs": [] // Final comment |
| 606 | +} |
| 607 | +""" |
| 608 | + |
| 609 | + stripped = MCPServersConfig._strip_json_comments(content_with_comments) |
| 610 | + config = MCPServersConfig.model_validate(json.loads(stripped)) |
| 611 | + |
| 612 | + assert "test_server" in config.servers |
| 613 | + server = config.servers["test_server"] |
| 614 | + assert isinstance(server, StdioServerConfig) |
| 615 | + assert server.command == "python test.py" |
| 616 | + |
| 617 | + |
| 618 | +def test_jsonc_comments_inside_strings_preserved(): |
| 619 | + """Test that // inside strings are not treated as comments.""" |
| 620 | + content_with_urls = """ |
| 621 | +{ |
| 622 | + "servers": { |
| 623 | + "web_server": { |
| 624 | + "type": "sse", |
| 625 | + "url": "https://example.com/api/endpoint" // This is a comment |
| 626 | + }, |
| 627 | + "protocol_server": { |
| 628 | + "type": "stdio", |
| 629 | + "command": "node server.js --url=http://localhost:3000" |
| 630 | + } |
| 631 | + } |
| 632 | +} |
| 633 | +""" |
| 634 | + |
| 635 | + stripped = MCPServersConfig._strip_json_comments(content_with_urls) |
| 636 | + config = MCPServersConfig.model_validate(json.loads(stripped)) |
| 637 | + |
| 638 | + web_server = config.servers["web_server"] |
| 639 | + assert isinstance(web_server, SSEServerConfig) |
| 640 | + assert web_server.url == "https://example.com/api/endpoint" |
| 641 | + |
| 642 | + protocol_server = config.servers["protocol_server"] |
| 643 | + assert isinstance(protocol_server, StdioServerConfig) |
| 644 | + # The // in the URL should be preserved |
| 645 | + assert "http://localhost:3000" in protocol_server.command |
| 646 | + |
| 647 | + |
| 648 | +def test_jsonc_escaped_quotes_handling(): |
| 649 | + """Test that escaped quotes in strings are handled correctly.""" |
| 650 | + content_with_escaped = """ |
| 651 | +{ |
| 652 | + "servers": { |
| 653 | + "test_server": { |
| 654 | + "type": "stdio", |
| 655 | + "command": "python -c \\"print('Hello // World')\\"", // Comment after escaped quotes |
| 656 | + "description": "Server with \\"escaped quotes\\" and // in string" |
| 657 | + } |
| 658 | + } |
| 659 | +} |
| 660 | +""" |
| 661 | + |
| 662 | + stripped = MCPServersConfig._strip_json_comments(content_with_escaped) |
| 663 | + config = MCPServersConfig.model_validate(json.loads(stripped)) |
| 664 | + |
| 665 | + server = config.servers["test_server"] |
| 666 | + assert isinstance(server, StdioServerConfig) |
| 667 | + # The command should preserve the escaped quotes and // inside the string |
| 668 | + assert server.command == "python -c \"print('Hello // World')\"" |
| 669 | + |
| 670 | + |
| 671 | +def test_from_file_with_jsonc_comments(tmp_path: Path): |
| 672 | + """Test loading JSONC file with comments via from_file method.""" |
| 673 | + jsonc_content = """ |
| 674 | +{ |
| 675 | + // Configuration for MCP servers |
| 676 | + "inputs": [ |
| 677 | + { |
| 678 | + "type": "promptString", |
| 679 | + "id": "api-key", // Secret API key |
| 680 | + "description": "API Key for authentication" |
| 681 | + } |
| 682 | + ], |
| 683 | + "servers": { |
| 684 | + // Main server configuration |
| 685 | + "main_server": { |
| 686 | + "type": "sse", |
| 687 | + "url": "https://api.example.com/mcp/sse", // Production URL |
| 688 | + "headers": { |
| 689 | + "Authorization": "Bearer ${input:api-key}" // Dynamic token |
| 690 | + } |
| 691 | + } |
| 692 | + } |
| 693 | + // End of configuration |
| 694 | +} |
| 695 | +""" |
| 696 | + |
| 697 | + config_file = tmp_path / "test_config.json" |
| 698 | + config_file.write_text(jsonc_content) |
| 699 | + |
| 700 | + inputs = {"api-key": "secret123"} |
| 701 | + |
| 702 | + # Should load successfully despite comments |
| 703 | + config = MCPServersConfig.from_file(config_file, inputs=inputs) |
| 704 | + |
| 705 | + # Verify input definitions were parsed |
| 706 | + assert config.inputs is not None |
| 707 | + assert len(config.inputs) == 1 |
| 708 | + assert config.inputs[0].id == "api-key" |
| 709 | + |
| 710 | + # Verify server configuration and input substitution |
| 711 | + server = config.servers["main_server"] |
| 712 | + assert isinstance(server, SSEServerConfig) |
| 713 | + assert server.url == "https://api.example.com/mcp/sse" |
| 714 | + assert server.headers == {"Authorization": "Bearer secret123"} |
| 715 | + |
| 716 | + |
| 717 | +def test_jsonc_multiline_strings_with_comments(): |
| 718 | + """Test that comments in multiline scenarios are handled correctly.""" |
| 719 | + content = """ |
| 720 | +{ |
| 721 | + "servers": { |
| 722 | + "test1": { |
| 723 | + // Comment before |
| 724 | + "type": "stdio", // Comment after |
| 725 | + "command": "python server.py" |
| 726 | + }, // Comment after object |
| 727 | + "test2": { "type": "sse", "url": "https://example.com" } // Inline comment |
| 728 | + } |
| 729 | +} |
| 730 | +""" |
| 731 | + |
| 732 | + stripped = MCPServersConfig._strip_json_comments(content) |
| 733 | + config = MCPServersConfig.model_validate(json.loads(stripped)) |
| 734 | + |
| 735 | + assert len(config.servers) == 2 |
| 736 | + assert "test1" in config.servers |
| 737 | + assert "test2" in config.servers |
| 738 | + |
| 739 | + test1 = config.servers["test1"] |
| 740 | + assert isinstance(test1, StdioServerConfig) |
| 741 | + assert test1.command == "python server.py" |
| 742 | + |
| 743 | + test2 = config.servers["test2"] |
| 744 | + assert isinstance(test2, SSEServerConfig) |
| 745 | + assert test2.url == "https://example.com" |
0 commit comments