mcp-win-stdio 0.3.2__tar.gz → 0.3.3__tar.gz

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.
Files changed (133) hide show
  1. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/PKG-INFO +22 -22
  2. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/README.md +1 -1
  3. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/packages/mcp-win-stdio-arch/pyproject.toml +2 -2
  4. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/packages/mcp-win-stdio-arch/src/mcp_win_stdio/arch/__init__.py +1 -1
  5. mcp_win_stdio-0.3.3/packages/mcp-win-stdio-arch/src/mcp_win_stdio/arch/scanner.py +459 -0
  6. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/packages/mcp-win-stdio-arch/src/mcp_win_stdio/arch/server.py +52 -19
  7. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/packages/mcp-win-stdio-db/pyproject.toml +2 -2
  8. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/packages/mcp-win-stdio-db/src/mcp_win_stdio/db/__init__.py +1 -1
  9. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/packages/mcp-win-stdio-db/src/mcp_win_stdio/db/server.py +4 -2
  10. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/packages/mcp-win-stdio-excel/pyproject.toml +2 -2
  11. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/packages/mcp-win-stdio-excel/src/mcp_win_stdio/excel/__init__.py +1 -1
  12. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/packages/mcp-win-stdio-excel-db/pyproject.toml +2 -2
  13. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/packages/mcp-win-stdio-excel-db/src/mcp_win_stdio/excel_db/__init__.py +1 -1
  14. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/packages/mcp-win-stdio-explorer/pyproject.toml +2 -2
  15. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/packages/mcp-win-stdio-explorer/src/mcp_win_stdio/explorer/__init__.py +1 -1
  16. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/packages/mcp-win-stdio-git/pyproject.toml +2 -2
  17. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/packages/mcp-win-stdio-git/src/mcp_win_stdio/git/__init__.py +1 -1
  18. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/packages/mcp-win-stdio-rag/pyproject.toml +2 -2
  19. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/packages/mcp-win-stdio-rag/src/mcp_win_stdio/rag/__init__.py +1 -1
  20. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/packages/mcp-win-stdio-rag/src/mcp_win_stdio/rag/server.py +67 -4
  21. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/packages/mcp-win-stdio-rag/src/mcp_win_stdio/rag/store.py +6 -2
  22. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/packages/mcp-win-stdio-ssh/pyproject.toml +2 -2
  23. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/packages/mcp-win-stdio-ssh/src/mcp_win_stdio/ssh/__init__.py +1 -1
  24. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/packages/mcp-win-stdio-tsc/pyproject.toml +2 -2
  25. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/packages/mcp-win-stdio-tsc/src/mcp_win_stdio/tsc/__init__.py +1 -1
  26. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/packages/mcp-win-stdio-word/pyproject.toml +2 -2
  27. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/packages/mcp-win-stdio-word/src/mcp_win_stdio/word/__init__.py +1 -1
  28. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/pyproject.toml +21 -21
  29. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/src/mcp_win_stdio/__init__.py +1 -1
  30. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/src/mcp_win_stdio/core/discovery.py +4 -4
  31. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/src/mcp_win_stdio/server/__init__.py +1 -1
  32. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/src/mcp_win_stdio/server/catalog.py +25 -5
  33. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/src/mcp_win_stdio/server/server.py +130 -16
  34. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/tests/test_cli.py +4 -3
  35. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/tests/test_server.py +39 -5
  36. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/uv.lock +11 -11
  37. mcp_win_stdio-0.3.2/packages/mcp-win-stdio-arch/src/mcp_win_stdio/arch/scanner.py +0 -250
  38. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/.agents/mcp_config.json +0 -0
  39. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/.github/workflows/ci.yml +0 -0
  40. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/.gitignore +0 -0
  41. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/.mcp.json +0 -0
  42. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/AGENTS.md +0 -0
  43. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/GEMINI.md +0 -0
  44. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/LICENSE +0 -0
  45. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/SECURITY.md +0 -0
  46. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/docs/excel_db_mcp.md +0 -0
  47. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/docs/project_auto_config_guide.md +0 -0
  48. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/packages/mcp-win-stdio-arch/README.md +0 -0
  49. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/packages/mcp-win-stdio-arch/src/mcp_win_stdio/arch/cli.py +0 -0
  50. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/packages/mcp-win-stdio-arch/src/mcp_win_stdio/arch/graph.py +0 -0
  51. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/packages/mcp-win-stdio-arch/src/mcp_win_stdio/arch/identity.py +0 -0
  52. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/packages/mcp-win-stdio-arch/src/mcp_win_stdio/arch/models.py +0 -0
  53. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/packages/mcp-win-stdio-arch/src/mcp_win_stdio/arch/renderers.py +0 -0
  54. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/packages/mcp-win-stdio-db/README.md +0 -0
  55. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/packages/mcp-win-stdio-db/src/mcp_win_stdio/db/__main__.py +0 -0
  56. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/packages/mcp-win-stdio-db/src/mcp_win_stdio/db/cli.py +0 -0
  57. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/packages/mcp-win-stdio-db/src/mcp_win_stdio/db/guide.py +0 -0
  58. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/packages/mcp-win-stdio-excel/README.md +0 -0
  59. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/packages/mcp-win-stdio-excel/src/mcp_win_stdio/excel/__main__.py +0 -0
  60. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/packages/mcp-win-stdio-excel/src/mcp_win_stdio/excel/cli.py +0 -0
  61. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/packages/mcp-win-stdio-excel/src/mcp_win_stdio/excel/guide.py +0 -0
  62. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/packages/mcp-win-stdio-excel/src/mcp_win_stdio/excel/server.py +0 -0
  63. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/packages/mcp-win-stdio-excel-db/README.md +0 -0
  64. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/packages/mcp-win-stdio-excel-db/src/mcp_win_stdio/excel_db/__main__.py +0 -0
  65. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/packages/mcp-win-stdio-excel-db/src/mcp_win_stdio/excel_db/auditor.py +0 -0
  66. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/packages/mcp-win-stdio-excel-db/src/mcp_win_stdio/excel_db/cli.py +0 -0
  67. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/packages/mcp-win-stdio-excel-db/src/mcp_win_stdio/excel_db/engine.py +0 -0
  68. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/packages/mcp-win-stdio-excel-db/src/mcp_win_stdio/excel_db/guide.py +0 -0
  69. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/packages/mcp-win-stdio-excel-db/src/mcp_win_stdio/excel_db/migrator.py +0 -0
  70. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/packages/mcp-win-stdio-excel-db/src/mcp_win_stdio/excel_db/server.py +0 -0
  71. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/packages/mcp-win-stdio-excel-db/src/mcp_win_stdio/excel_db/stream.py +0 -0
  72. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/packages/mcp-win-stdio-explorer/README.md +0 -0
  73. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/packages/mcp-win-stdio-explorer/src/mcp_win_stdio/explorer/__main__.py +0 -0
  74. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/packages/mcp-win-stdio-explorer/src/mcp_win_stdio/explorer/cli.py +0 -0
  75. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/packages/mcp-win-stdio-explorer/src/mcp_win_stdio/explorer/guide.py +0 -0
  76. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/packages/mcp-win-stdio-explorer/src/mcp_win_stdio/explorer/server.py +0 -0
  77. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/packages/mcp-win-stdio-git/README.md +0 -0
  78. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/packages/mcp-win-stdio-git/src/mcp_win_stdio/git/__main__.py +0 -0
  79. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/packages/mcp-win-stdio-git/src/mcp_win_stdio/git/cli.py +0 -0
  80. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/packages/mcp-win-stdio-git/src/mcp_win_stdio/git/guide.py +0 -0
  81. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/packages/mcp-win-stdio-git/src/mcp_win_stdio/git/server.py +0 -0
  82. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/packages/mcp-win-stdio-rag/README.md +0 -0
  83. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/packages/mcp-win-stdio-rag/src/mcp_win_stdio/rag/__main__.py +0 -0
  84. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/packages/mcp-win-stdio-rag/src/mcp_win_stdio/rag/cli.py +0 -0
  85. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/packages/mcp-win-stdio-rag/src/mcp_win_stdio/rag/crawler.py +0 -0
  86. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/packages/mcp-win-stdio-rag/src/mcp_win_stdio/rag/guide.py +0 -0
  87. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/packages/mcp-win-stdio-rag/src/mcp_win_stdio/rag/ingest.py +0 -0
  88. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/packages/mcp-win-stdio-ssh/README.md +0 -0
  89. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/packages/mcp-win-stdio-ssh/src/mcp_win_stdio/ssh/__main__.py +0 -0
  90. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/packages/mcp-win-stdio-ssh/src/mcp_win_stdio/ssh/cli.py +0 -0
  91. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/packages/mcp-win-stdio-ssh/src/mcp_win_stdio/ssh/connection.py +0 -0
  92. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/packages/mcp-win-stdio-ssh/src/mcp_win_stdio/ssh/guide.py +0 -0
  93. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/packages/mcp-win-stdio-ssh/src/mcp_win_stdio/ssh/pty_session.py +0 -0
  94. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/packages/mcp-win-stdio-ssh/src/mcp_win_stdio/ssh/server.py +0 -0
  95. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/packages/mcp-win-stdio-ssh/src/mcp_win_stdio/ssh/sftp_ops.py +0 -0
  96. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/packages/mcp-win-stdio-ssh/src/mcp_win_stdio/ssh/tunnels.py +0 -0
  97. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/packages/mcp-win-stdio-tsc/README.md +0 -0
  98. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/packages/mcp-win-stdio-tsc/src/mcp_win_stdio/tsc/__main__.py +0 -0
  99. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/packages/mcp-win-stdio-tsc/src/mcp_win_stdio/tsc/cli.py +0 -0
  100. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/packages/mcp-win-stdio-tsc/src/mcp_win_stdio/tsc/guide.py +0 -0
  101. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/packages/mcp-win-stdio-tsc/src/mcp_win_stdio/tsc/server.py +0 -0
  102. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/packages/mcp-win-stdio-word/README.md +0 -0
  103. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/packages/mcp-win-stdio-word/src/mcp_win_stdio/word/__main__.py +0 -0
  104. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/packages/mcp-win-stdio-word/src/mcp_win_stdio/word/cli.py +0 -0
  105. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/packages/mcp-win-stdio-word/src/mcp_win_stdio/word/guide.py +0 -0
  106. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/packages/mcp-win-stdio-word/src/mcp_win_stdio/word/server.py +0 -0
  107. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/requirements-dev.txt +0 -0
  108. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/requirements.txt +0 -0
  109. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/scripts/build_all.py +0 -0
  110. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/scripts/bump_version.py +0 -0
  111. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/scripts/publish_safe.py +0 -0
  112. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/src/mcp_win_stdio/__main__.py +0 -0
  113. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/src/mcp_win_stdio/cli.py +0 -0
  114. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/src/mcp_win_stdio/core/clients.py +0 -0
  115. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/src/mcp_win_stdio/core/config.py +0 -0
  116. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/src/mcp_win_stdio/core/dialogs.py +0 -0
  117. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/src/mcp_win_stdio/core/environment.py +0 -0
  118. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/src/mcp_win_stdio/core/installer.py +0 -0
  119. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/src/mcp_win_stdio/core/updater.py +0 -0
  120. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/src/mcp_win_stdio/core/wizard.py +0 -0
  121. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/src/mcp_win_stdio/core/workflows.py +0 -0
  122. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/src/mcp_win_stdio/server/__main__.py +0 -0
  123. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/src/mcp_win_stdio/server/cli.py +0 -0
  124. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/src/mcp_win_stdio/server/config.py +0 -0
  125. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/src/mcp_win_stdio/server/discovery.py +0 -0
  126. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/src/mcp_win_stdio/server/guide.py +0 -0
  127. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/src/mcp_win_stdio/server/process_pool.py +0 -0
  128. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/tests/conftest.py +0 -0
  129. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/tests/test_audit.py +0 -0
  130. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/tests/test_db_claude_config.py +0 -0
  131. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/tests/test_git.py +0 -0
  132. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/tests/test_new_tools.py +0 -0
  133. {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/tests/test_workflows.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: mcp-win-stdio
3
- Version: 0.3.2
3
+ Version: 0.3.3
4
4
  Summary: Universal Windows-optimized Model Context Protocol (MCP) suite & dynamic on-demand tool gateway for AI IDEs and CLI agents (Gemini, Claude, ChatGPT, Cursor, Windsurf, Kiro, OpenCode).
5
5
  Author: Mohan Kumar Indala
6
6
  License-Expression: MIT
@@ -21,20 +21,20 @@ Classifier: Topic :: Software Development :: Libraries
21
21
  Requires-Python: >=3.10
22
22
  Requires-Dist: mcp>=1.2.0
23
23
  Provides-Extra: all
24
- Requires-Dist: mcp-win-stdio-arch>=0.3.2; extra == 'all'
25
- Requires-Dist: mcp-win-stdio-db>=0.3.2; extra == 'all'
26
- Requires-Dist: mcp-win-stdio-excel-db>=0.3.2; extra == 'all'
27
- Requires-Dist: mcp-win-stdio-excel>=0.3.2; extra == 'all'
28
- Requires-Dist: mcp-win-stdio-explorer>=0.3.2; extra == 'all'
29
- Requires-Dist: mcp-win-stdio-git>=0.3.2; extra == 'all'
30
- Requires-Dist: mcp-win-stdio-rag>=0.3.2; extra == 'all'
31
- Requires-Dist: mcp-win-stdio-ssh>=0.3.2; extra == 'all'
32
- Requires-Dist: mcp-win-stdio-tsc>=0.3.2; extra == 'all'
33
- Requires-Dist: mcp-win-stdio-word>=0.3.2; extra == 'all'
24
+ Requires-Dist: mcp-win-stdio-arch>=0.3.3; extra == 'all'
25
+ Requires-Dist: mcp-win-stdio-db>=0.3.3; extra == 'all'
26
+ Requires-Dist: mcp-win-stdio-excel-db>=0.3.3; extra == 'all'
27
+ Requires-Dist: mcp-win-stdio-excel>=0.3.3; extra == 'all'
28
+ Requires-Dist: mcp-win-stdio-explorer>=0.3.3; extra == 'all'
29
+ Requires-Dist: mcp-win-stdio-git>=0.3.3; extra == 'all'
30
+ Requires-Dist: mcp-win-stdio-rag>=0.3.3; extra == 'all'
31
+ Requires-Dist: mcp-win-stdio-ssh>=0.3.3; extra == 'all'
32
+ Requires-Dist: mcp-win-stdio-tsc>=0.3.3; extra == 'all'
33
+ Requires-Dist: mcp-win-stdio-word>=0.3.3; extra == 'all'
34
34
  Provides-Extra: arch
35
- Requires-Dist: mcp-win-stdio-arch>=0.3.2; extra == 'arch'
35
+ Requires-Dist: mcp-win-stdio-arch>=0.3.3; extra == 'arch'
36
36
  Provides-Extra: db
37
- Requires-Dist: mcp-win-stdio-db>=0.3.2; extra == 'db'
37
+ Requires-Dist: mcp-win-stdio-db>=0.3.3; extra == 'db'
38
38
  Provides-Extra: dev
39
39
  Requires-Dist: build>=1.2.0; extra == 'dev'
40
40
  Requires-Dist: hatchling>=1.25.0; extra == 'dev'
@@ -42,21 +42,21 @@ Requires-Dist: pytest>=8.0.0; extra == 'dev'
42
42
  Requires-Dist: ruff>=0.9.0; extra == 'dev'
43
43
  Requires-Dist: twine>=5.0.0; extra == 'dev'
44
44
  Provides-Extra: excel
45
- Requires-Dist: mcp-win-stdio-excel>=0.3.2; extra == 'excel'
45
+ Requires-Dist: mcp-win-stdio-excel>=0.3.3; extra == 'excel'
46
46
  Provides-Extra: excel-db
47
- Requires-Dist: mcp-win-stdio-excel-db>=0.3.2; extra == 'excel-db'
47
+ Requires-Dist: mcp-win-stdio-excel-db>=0.3.3; extra == 'excel-db'
48
48
  Provides-Extra: explorer
49
- Requires-Dist: mcp-win-stdio-explorer>=0.3.2; extra == 'explorer'
49
+ Requires-Dist: mcp-win-stdio-explorer>=0.3.3; extra == 'explorer'
50
50
  Provides-Extra: git
51
- Requires-Dist: mcp-win-stdio-git>=0.3.2; extra == 'git'
51
+ Requires-Dist: mcp-win-stdio-git>=0.3.3; extra == 'git'
52
52
  Provides-Extra: rag
53
- Requires-Dist: mcp-win-stdio-rag>=0.3.2; extra == 'rag'
53
+ Requires-Dist: mcp-win-stdio-rag>=0.3.3; extra == 'rag'
54
54
  Provides-Extra: ssh
55
- Requires-Dist: mcp-win-stdio-ssh>=0.3.2; extra == 'ssh'
55
+ Requires-Dist: mcp-win-stdio-ssh>=0.3.3; extra == 'ssh'
56
56
  Provides-Extra: tsc
57
- Requires-Dist: mcp-win-stdio-tsc>=0.3.2; extra == 'tsc'
57
+ Requires-Dist: mcp-win-stdio-tsc>=0.3.3; extra == 'tsc'
58
58
  Provides-Extra: word
59
- Requires-Dist: mcp-win-stdio-word>=0.3.2; extra == 'word'
59
+ Requires-Dist: mcp-win-stdio-word>=0.3.3; extra == 'word'
60
60
  Description-Content-Type: text/markdown
61
61
 
62
62
  # mcp-win-stdio
@@ -145,7 +145,7 @@ mws
145
145
  Output:
146
146
  ```text
147
147
  ============================================================================
148
- 🚀 mcp-win-stdio — Windows Model Context Protocol Suite (v0.3.2)
148
+ 🚀 mcp-win-stdio — Windows Model Context Protocol Suite (v0.3.3)
149
149
  ============================================================================
150
150
 
151
151
  Single-Source Hub: C:\Users\admin\.mcp-win-stdio
@@ -84,7 +84,7 @@ mws
84
84
  Output:
85
85
  ```text
86
86
  ============================================================================
87
- 🚀 mcp-win-stdio — Windows Model Context Protocol Suite (v0.3.2)
87
+ 🚀 mcp-win-stdio — Windows Model Context Protocol Suite (v0.3.3)
88
88
  ============================================================================
89
89
 
90
90
  Single-Source Hub: C:\Users\admin\.mcp-win-stdio
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "mcp-win-stdio-arch"
7
- version = "0.3.2"
7
+ version = "0.3.3"
8
8
  description = "Universal Architecture Knowledge Graph & Blueprint Model Context Protocol (MCP) server for full-stack codebases (Next.js, NestJS, Drizzle, PostgreSQL/MySQL)."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -29,7 +29,7 @@ classifiers = [
29
29
  ]
30
30
 
31
31
  dependencies = [
32
- "mcp-win-stdio>=0.3.2",
32
+ "mcp-win-stdio>=0.3.3",
33
33
  "mcp>=1.2.0",
34
34
  "networkx>=3.2.1",
35
35
  ]
@@ -3,4 +3,4 @@ Universal Architecture Blueprint & Knowledge Graph module.
3
3
  Part of mcp-win-stdio.
4
4
  """
5
5
 
6
- __version__ = "0.3.2"
6
+ __version__ = "0.3.3"
@@ -0,0 +1,459 @@
1
+ """
2
+ Universal Full-Stack Architecture AST Scanner
3
+ Part of mcp-win-stdio-arch.
4
+
5
+ Auto-discovers and maps full-stack architectures:
6
+ Frontend (Next.js / React / TanStack Query / SWR)
7
+ -> Ingress Routes (NestJS Controllers, Next.js App Router API Routes)
8
+ -> Domain Logic (NestJS Services, Domain Handlers)
9
+ -> Storage Entities (Drizzle ORM pgTable/mysqlTable/sqliteTable).
10
+ """
11
+
12
+ import os
13
+ import re
14
+ from pathlib import Path
15
+ from typing import Any, Dict, List, Optional, Set
16
+
17
+ from .graph import ArchitectureGraph
18
+ from .identity import NodeIdentifier, normalize_route_path
19
+ from .models import (
20
+ ArchitectureEdge,
21
+ ArchitectureNode,
22
+ Confidence,
23
+ EdgeEvidence,
24
+ EdgeType,
25
+ EvidenceLocation,
26
+ NodeKind,
27
+ Resolution,
28
+ )
29
+
30
+
31
+ class ArchitectureScanner:
32
+ """
33
+ Universal scanner for full-stack TypeScript / JavaScript architectures.
34
+ Auto-discovers frontend, backend, and database schema layers.
35
+ """
36
+
37
+ def __init__(
38
+ self,
39
+ workspace_root: Optional[str] = None,
40
+ frontend_root: Optional[str] = None,
41
+ backend_root: Optional[str] = None,
42
+ schema_dir: Optional[str] = None,
43
+ ) -> None:
44
+ self.workspace_root = Path(workspace_root or os.getcwd()).resolve()
45
+
46
+ # 1. Resolve frontend root
47
+ if frontend_root:
48
+ self.frontend_root = Path(frontend_root).resolve()
49
+ else:
50
+ candidates = [
51
+ self.workspace_root / "rfc_location_booking_frontend",
52
+ self.workspace_root / "frontend",
53
+ self.workspace_root / "client",
54
+ self.workspace_root / "web",
55
+ self.workspace_root / "apps" / "web",
56
+ self.workspace_root / "apps" / "frontend",
57
+ ]
58
+ self.frontend_root = next((c for c in candidates if c.is_dir()), self.workspace_root)
59
+
60
+ # 2. Resolve backend root
61
+ if backend_root:
62
+ self.backend_root = Path(backend_root).resolve()
63
+ else:
64
+ candidates = [
65
+ self.workspace_root / "rfc_location_booking_backend",
66
+ self.workspace_root / "backend",
67
+ self.workspace_root / "server",
68
+ self.workspace_root / "api",
69
+ self.workspace_root / "apps" / "api",
70
+ self.workspace_root / "apps" / "backend",
71
+ ]
72
+ self.backend_root = next((c for c in candidates if c.is_dir()), self.workspace_root)
73
+
74
+ # 3. Explicit schema directory or auto-discover
75
+ self.explicit_schema_dir = Path(schema_dir).resolve() if schema_dir else None
76
+
77
+ # Diagnostic tracker for actionable feedback
78
+ self.diagnostics: Dict[str, Any] = {
79
+ "workspace_root": str(self.workspace_root),
80
+ "frontend_root": str(self.frontend_root),
81
+ "backend_root": str(self.backend_root),
82
+ "schema_dirs_scanned": [],
83
+ "tables_found": 0,
84
+ "routes_found": 0,
85
+ "services_found": 0,
86
+ "client_hooks_found": 0,
87
+ "warnings": [],
88
+ }
89
+
90
+ def _discover_schema_dirs(self) -> List[Path]:
91
+ """Discover directories containing Drizzle ORM schemas."""
92
+ if self.explicit_schema_dir and self.explicit_schema_dir.exists():
93
+ return [self.explicit_schema_dir]
94
+
95
+ found: List[Path] = []
96
+ candidates = [
97
+ self.backend_root / "src" / "database" / "schema",
98
+ self.backend_root / "src" / "db" / "schema",
99
+ self.backend_root / "src" / "db",
100
+ self.backend_root / "src" / "database",
101
+ self.backend_root / "drizzle",
102
+ self.workspace_root / "drizzle",
103
+ self.workspace_root / "src" / "db" / "schema",
104
+ self.workspace_root / "src" / "db",
105
+ self.workspace_root / "src" / "database" / "schema",
106
+ self.workspace_root / "packages" / "db" / "src" / "schema",
107
+ self.workspace_root / "packages" / "database" / "src" / "schema",
108
+ ]
109
+
110
+ # Check drizzle.config.ts / drizzle.config.js for custom schema entry
111
+ for cfg_name in ("drizzle.config.ts", "drizzle.config.js", "drizzle.config.json"):
112
+ for root_target in (self.backend_root, self.workspace_root):
113
+ cfg_path = root_target / cfg_name
114
+ if cfg_path.exists():
115
+ try:
116
+ content = cfg_path.read_text(encoding="utf-8")
117
+ m = re.search(r"schema\s*:\s*['\"]([^'\"]+)['\"]", content)
118
+ if m:
119
+ raw_schema = m.group(1)
120
+ resolved = (root_target / raw_schema).resolve()
121
+ if resolved.is_dir():
122
+ found.append(resolved)
123
+ elif resolved.parent.is_dir():
124
+ found.append(resolved.parent)
125
+ except Exception:
126
+ pass
127
+
128
+ for c in candidates:
129
+ if c.is_dir() and c not in found:
130
+ found.append(c)
131
+
132
+ return found
133
+
134
+ def scan_all(self, graph: ArchitectureGraph, domain: Optional[str] = None) -> Dict[str, Any]:
135
+ """Execute full architectural scan across schemas, backend, and frontend."""
136
+ # 1. Scan Drizzle DB Schemas
137
+ self.scan_drizzle_schemas(graph, domain)
138
+ # 2. Scan Backend (NestJS Controllers, Next.js App Router API Routes)
139
+ self.scan_backend_routes(graph, domain)
140
+ # 3. Scan Frontend (Next.js TanStack Query / SWR / React Hooks)
141
+ self.scan_frontend_clients(graph, domain)
142
+
143
+ # Assemble diagnostics
144
+ total_nodes = (
145
+ self.diagnostics["tables_found"]
146
+ + self.diagnostics["routes_found"]
147
+ + self.diagnostics["services_found"]
148
+ + self.diagnostics["client_hooks_found"]
149
+ )
150
+ if total_nodes == 0:
151
+ self.diagnostics["warnings"].append(
152
+ f"No architectural entities matched in '{self.workspace_root}'. "
153
+ "Ensure project contains Drizzle schemas, NestJS controllers, or Next.js API routes, "
154
+ "or configure explicit workspace_root / backend_root / schema_dir."
155
+ )
156
+
157
+ return self.diagnostics
158
+
159
+ def scan_drizzle_schemas(self, graph: ArchitectureGraph, domain: Optional[str] = None) -> None:
160
+ """Scan Drizzle schema definitions (pgTable, mysqlTable, sqliteTable, schema.table)."""
161
+ schema_dirs = self._discover_schema_dirs()
162
+ self.diagnostics["schema_dirs_scanned"] = [str(d) for d in schema_dirs]
163
+
164
+ if not schema_dirs:
165
+ return
166
+
167
+ table_var_map: Dict[str, str] = {} # varName -> tableNodeId
168
+
169
+ for schema_dir in schema_dirs:
170
+ schema_files = list(schema_dir.glob("*.ts")) + list(schema_dir.glob("*.js"))
171
+ for sfile in schema_files:
172
+ try:
173
+ content = sfile.read_text(encoding="utf-8")
174
+ except Exception:
175
+ continue
176
+
177
+ try:
178
+ rel_path = str(sfile.relative_to(self.workspace_root))
179
+ except ValueError:
180
+ rel_path = str(sfile)
181
+
182
+ # Match: export const <varName> = pgTable|mysqlTable|sqliteTable|schema.table('tableName', ...)
183
+ table_matches = re.finditer(
184
+ r"export\s+const\s+([a-zA-Z0-9_]+)\s*=\s*(?:pgTable|mysqlTable|sqliteTable|[a-zA-Z0-9_]+\.table)\s*\(\s*['\"]([^'\"]+)['\"]",
185
+ content,
186
+ )
187
+
188
+ for m in table_matches:
189
+ var_name = m.group(1)
190
+ table_name = m.group(2)
191
+ node_id = NodeIdentifier.for_storage_entity("public", table_name)
192
+
193
+ table_node = ArchitectureNode(
194
+ id=node_id,
195
+ kind=NodeKind.STORAGE_ENTITY,
196
+ name=table_name,
197
+ file_path=rel_path,
198
+ domain=domain or "database",
199
+ metadata={"var_name": var_name, "orm": "drizzle"},
200
+ )
201
+ graph.add_node(table_node)
202
+ table_var_map[var_name] = node_id
203
+ self.diagnostics["tables_found"] += 1
204
+
205
+ # Parse foreign keys: .references(() => otherTable.id)
206
+ fk_matches = re.finditer(
207
+ r"export\s+const\s+([a-zA-Z0-9_]+)\s*=\s*(?:pgTable|mysqlTable|sqliteTable|[a-zA-Z0-9_]+\.table).*?\.references\(\s*\(\)\s*=>\s*([a-zA-Z0-9_]+)\.",
208
+ content,
209
+ re.DOTALL,
210
+ )
211
+ for fk in fk_matches:
212
+ src_var = fk.group(1)
213
+ tgt_var = fk.group(2)
214
+ if src_var in table_var_map and tgt_var in table_var_map and src_var != tgt_var:
215
+ src_id = table_var_map[src_var]
216
+ tgt_id = table_var_map[tgt_var]
217
+ edge_id = NodeIdentifier.for_edge(src_id, "REFERENCES_FK", tgt_id)
218
+ if not graph.get_edge(edge_id):
219
+ graph.add_edge(
220
+ ArchitectureEdge(
221
+ id=edge_id,
222
+ source=src_id,
223
+ target=tgt_id,
224
+ type=EdgeType.REFERENCES_FK,
225
+ confidence=Confidence.HIGH,
226
+ resolution=Resolution.STATIC,
227
+ evidence=EdgeEvidence(
228
+ rule_id="DRIZZLE_FK_REFERENCES",
229
+ locations=[EvidenceLocation(file_path=rel_path)],
230
+ explanation=f"FK: {src_var} -> {tgt_var}",
231
+ ),
232
+ )
233
+ )
234
+
235
+ def scan_backend_routes(self, graph: ArchitectureGraph, domain: Optional[str] = None) -> None:
236
+ """Scan backend endpoints from NestJS controllers and Next.js App Router route handlers."""
237
+ # 1. NestJS Controllers
238
+ controller_files: List[Path] = []
239
+ for root_target in (self.backend_root, self.workspace_root):
240
+ if root_target.exists():
241
+ controller_files.extend(root_target.rglob("*controller.ts"))
242
+ controller_files.extend(root_target.rglob("*controller.js"))
243
+
244
+ seen_ctrl_files: Set[Path] = set()
245
+ for ctrl_file in controller_files:
246
+ resolved_ctrl = ctrl_file.resolve()
247
+ if resolved_ctrl in seen_ctrl_files:
248
+ continue
249
+ seen_ctrl_files.add(resolved_ctrl)
250
+
251
+ try:
252
+ content = ctrl_file.read_text(encoding="utf-8")
253
+ except Exception:
254
+ continue
255
+
256
+ try:
257
+ rel_path = str(ctrl_file.relative_to(self.workspace_root))
258
+ except ValueError:
259
+ rel_path = str(ctrl_file)
260
+
261
+ # Match @Controller('prefix')
262
+ prefix_match = re.search(r"@Controller\(\s*['\"]([^'\"]*)['\"]\s*\)", content)
263
+ base_prefix = prefix_match.group(1) if prefix_match else ""
264
+ if not base_prefix.startswith("/") and base_prefix:
265
+ base_prefix = "/" + base_prefix
266
+
267
+ # Match routes: @Get(':id'), @Post(), etc.
268
+ method_matches = re.finditer(
269
+ r"@(Get|Post|Put|Patch|Delete)\(\s*(?:['\"]([^'\"]*)['\"])?\s*\)\s*(?:async\s+)?([a-zA-Z0-9_]+)\s*\(",
270
+ content,
271
+ )
272
+
273
+ for mm in method_matches:
274
+ http_method = mm.group(1).upper()
275
+ sub_path = mm.group(2) or ""
276
+ fn_name = mm.group(3)
277
+
278
+ full_path = f"{base_prefix}/{sub_path}".replace("//", "/")
279
+ norm_route_id = NodeIdentifier.for_ingress_route(http_method, full_path)
280
+
281
+ route_node = ArchitectureNode(
282
+ id=norm_route_id,
283
+ kind=NodeKind.INGRESS_ROUTE,
284
+ name=f"{http_method} {full_path}",
285
+ file_path=rel_path,
286
+ domain=domain or "backend",
287
+ metadata={"handler": fn_name, "method": http_method, "path": full_path, "framework": "nestjs"},
288
+ )
289
+ graph.add_node(route_node)
290
+ self.diagnostics["routes_found"] += 1
291
+
292
+ # Link to Service in same module if called
293
+ svc_call_match = re.search(r"this\.([a-zA-Z0-9_]+Service)\.([a-zA-Z0-9_]+)\(", content)
294
+ if svc_call_match:
295
+ svc_class = svc_call_match.group(1)
296
+ svc_method = svc_call_match.group(2)
297
+ svc_id = NodeIdentifier.for_domain_logic("backend", rel_path, svc_class, svc_method)
298
+ if not graph.has_node(svc_id):
299
+ graph.add_node(
300
+ ArchitectureNode(
301
+ id=svc_id,
302
+ kind=NodeKind.DOMAIN_LOGIC,
303
+ name=f"{svc_class}.{svc_method}",
304
+ file_path=rel_path,
305
+ domain=domain or "backend",
306
+ )
307
+ )
308
+ self.diagnostics["services_found"] += 1
309
+
310
+ edge_id = NodeIdentifier.for_edge(norm_route_id, "CALLS", svc_id)
311
+ if not graph.get_edge(edge_id):
312
+ graph.add_edge(
313
+ ArchitectureEdge(
314
+ id=edge_id,
315
+ source=norm_route_id,
316
+ target=svc_id,
317
+ type=EdgeType.CALLS,
318
+ confidence=Confidence.HIGH,
319
+ resolution=Resolution.STATIC,
320
+ evidence=EdgeEvidence(
321
+ rule_id="NESTJS_CONTROLLER_SERVICE_CALL",
322
+ locations=[EvidenceLocation(file_path=rel_path, symbol=fn_name)],
323
+ ),
324
+ )
325
+ )
326
+
327
+ # 2. Next.js App Router API Routes (app/**/route.ts)
328
+ app_dirs = [
329
+ self.backend_root / "src" / "app",
330
+ self.backend_root / "app",
331
+ self.frontend_root / "src" / "app",
332
+ self.frontend_root / "app",
333
+ self.workspace_root / "src" / "app",
334
+ self.workspace_root / "app",
335
+ ]
336
+ seen_route_files: Set[Path] = set()
337
+ for app_dir in app_dirs:
338
+ if not app_dir.is_dir():
339
+ continue
340
+ for rfile in app_dir.rglob("route.ts"):
341
+ resolved_rf = rfile.resolve()
342
+ if resolved_rf in seen_route_files:
343
+ continue
344
+ seen_route_files.add(resolved_rf)
345
+
346
+ try:
347
+ content = rfile.read_text(encoding="utf-8")
348
+ except Exception:
349
+ continue
350
+
351
+ try:
352
+ rel_path = str(rfile.relative_to(self.workspace_root))
353
+ except ValueError:
354
+ rel_path = str(rfile)
355
+
356
+ # Route path is the directory path relative to app/
357
+ route_subpath = rfile.parent.relative_to(app_dir).as_posix()
358
+ api_path = f"/{route_subpath}" if not route_subpath.startswith("/") else route_subpath
359
+
360
+ # Match export async function GET/POST/PUT/DELETE/PATCH
361
+ handlers = re.findall(
362
+ r"export\s+async\s+function\s+(GET|POST|PUT|DELETE|PATCH)\s*\(",
363
+ content,
364
+ )
365
+ for http_method in handlers:
366
+ norm_route_id = NodeIdentifier.for_ingress_route(http_method, api_path)
367
+ route_node = ArchitectureNode(
368
+ id=norm_route_id,
369
+ kind=NodeKind.INGRESS_ROUTE,
370
+ name=f"{http_method} {api_path}",
371
+ file_path=rel_path,
372
+ domain=domain or "api",
373
+ metadata={"path": api_path, "method": http_method, "framework": "nextjs-app-router"},
374
+ )
375
+ graph.add_node(route_node)
376
+ self.diagnostics["routes_found"] += 1
377
+
378
+ def scan_frontend_clients(self, graph: ArchitectureGraph, domain: Optional[str] = None) -> None:
379
+ """Scan frontend React Query / SWR / API hooks and map them to backend routes."""
380
+ hook_dirs: List[Path] = []
381
+ for root_target in (self.frontend_root, self.workspace_root):
382
+ for sub in ("features", "src/features", "hooks", "src/hooks", "components", "src/components"):
383
+ target = root_target / sub
384
+ if target.is_dir() and target not in hook_dirs:
385
+ hook_dirs.append(target)
386
+
387
+ if not hook_dirs:
388
+ return
389
+
390
+ seen_hook_files: Set[Path] = set()
391
+ for hdir in hook_dirs:
392
+ for hook_file in hdir.rglob("*.ts*"):
393
+ resolved_hf = hook_file.resolve()
394
+ if resolved_hf in seen_hook_files:
395
+ continue
396
+ seen_hook_files.add(resolved_hf)
397
+
398
+ try:
399
+ content = hook_file.read_text(encoding="utf-8")
400
+ except Exception:
401
+ continue
402
+
403
+ try:
404
+ rel_path = str(hook_file.relative_to(self.workspace_root))
405
+ except ValueError:
406
+ rel_path = str(hook_file)
407
+
408
+ # Match hooks: export function useEstimationQuery or const useX =
409
+ hook_match = re.search(r"export\s+(?:function|const)\s+(use[a-zA-Z0-9_]+)", content)
410
+ if not hook_match:
411
+ continue
412
+
413
+ hook_name = hook_match.group(1)
414
+ hook_id = NodeIdentifier.for_client_transport("frontend", rel_path, hook_name)
415
+
416
+ # Look for endpoint pattern: e.g. /api/v1/prop/estimations or axios.get('/api/...')
417
+ endpoint_match = re.search(r"['\"](\/api\/[a-zA-Z0-9_\-\/]+(?:\$\{[^}]+\})?)['\"]", content)
418
+ endpoint_url = endpoint_match.group(1) if endpoint_match else None
419
+
420
+ client_node = ArchitectureNode(
421
+ id=hook_id,
422
+ kind=NodeKind.CLIENT_TRANSPORT,
423
+ name=hook_name,
424
+ file_path=rel_path,
425
+ domain=domain or "frontend",
426
+ metadata={"endpoint": endpoint_url},
427
+ )
428
+ graph.add_node(client_node)
429
+ self.diagnostics["client_hooks_found"] += 1
430
+
431
+ # Match with backend route if endpoint matches
432
+ if endpoint_url:
433
+ for rnode in graph.get_nodes_by_kind(NodeKind.INGRESS_ROUTE):
434
+ r_path = rnode.metadata.get("path", "")
435
+ if normalize_route_path(r_path) == normalize_route_path(endpoint_url) or (
436
+ r_path and r_path in endpoint_url
437
+ ):
438
+ edge_id = NodeIdentifier.for_edge(hook_id, "ROUTES_TO", rnode.id)
439
+ if not graph.get_edge(edge_id):
440
+ graph.add_edge(
441
+ ArchitectureEdge(
442
+ id=edge_id,
443
+ source=hook_id,
444
+ target=rnode.id,
445
+ type=EdgeType.ROUTES_TO,
446
+ confidence=Confidence.HIGH,
447
+ resolution=Resolution.INFERRED,
448
+ evidence=EdgeEvidence(
449
+ rule_id="HTTP_CLIENT_ROUTE_MATCH",
450
+ locations=[EvidenceLocation(file_path=rel_path, symbol=hook_name)],
451
+ explanation=f"Matched {endpoint_url} to backend {r_path}",
452
+ ),
453
+ )
454
+ )
455
+ break
456
+
457
+
458
+ # Backwards-compatibility alias
459
+ ShowreelScanner = ArchitectureScanner