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.
- {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/PKG-INFO +22 -22
- {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/README.md +1 -1
- {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/packages/mcp-win-stdio-arch/pyproject.toml +2 -2
- {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
- mcp_win_stdio-0.3.3/packages/mcp-win-stdio-arch/src/mcp_win_stdio/arch/scanner.py +459 -0
- {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
- {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/packages/mcp-win-stdio-db/pyproject.toml +2 -2
- {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
- {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
- {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/packages/mcp-win-stdio-excel/pyproject.toml +2 -2
- {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
- {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/packages/mcp-win-stdio-excel-db/pyproject.toml +2 -2
- {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
- {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/packages/mcp-win-stdio-explorer/pyproject.toml +2 -2
- {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
- {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/packages/mcp-win-stdio-git/pyproject.toml +2 -2
- {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
- {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/packages/mcp-win-stdio-rag/pyproject.toml +2 -2
- {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
- {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
- {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
- {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/packages/mcp-win-stdio-ssh/pyproject.toml +2 -2
- {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
- {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/packages/mcp-win-stdio-tsc/pyproject.toml +2 -2
- {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
- {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/packages/mcp-win-stdio-word/pyproject.toml +2 -2
- {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
- {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/pyproject.toml +21 -21
- {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/src/mcp_win_stdio/__init__.py +1 -1
- {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/src/mcp_win_stdio/core/discovery.py +4 -4
- {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/src/mcp_win_stdio/server/__init__.py +1 -1
- {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/src/mcp_win_stdio/server/catalog.py +25 -5
- {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/src/mcp_win_stdio/server/server.py +130 -16
- {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/tests/test_cli.py +4 -3
- {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/tests/test_server.py +39 -5
- {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/uv.lock +11 -11
- mcp_win_stdio-0.3.2/packages/mcp-win-stdio-arch/src/mcp_win_stdio/arch/scanner.py +0 -250
- {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/.agents/mcp_config.json +0 -0
- {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/.github/workflows/ci.yml +0 -0
- {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/.gitignore +0 -0
- {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/.mcp.json +0 -0
- {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/AGENTS.md +0 -0
- {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/GEMINI.md +0 -0
- {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/LICENSE +0 -0
- {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/SECURITY.md +0 -0
- {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/docs/excel_db_mcp.md +0 -0
- {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/docs/project_auto_config_guide.md +0 -0
- {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/packages/mcp-win-stdio-arch/README.md +0 -0
- {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
- {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
- {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
- {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
- {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
- {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/packages/mcp-win-stdio-db/README.md +0 -0
- {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
- {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
- {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
- {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/packages/mcp-win-stdio-excel/README.md +0 -0
- {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
- {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
- {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
- {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
- {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/packages/mcp-win-stdio-excel-db/README.md +0 -0
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/packages/mcp-win-stdio-explorer/README.md +0 -0
- {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
- {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
- {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
- {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
- {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/packages/mcp-win-stdio-git/README.md +0 -0
- {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
- {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
- {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
- {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
- {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/packages/mcp-win-stdio-rag/README.md +0 -0
- {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
- {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
- {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
- {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
- {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
- {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/packages/mcp-win-stdio-ssh/README.md +0 -0
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/packages/mcp-win-stdio-tsc/README.md +0 -0
- {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
- {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
- {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
- {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
- {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/packages/mcp-win-stdio-word/README.md +0 -0
- {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
- {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
- {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
- {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
- {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/requirements-dev.txt +0 -0
- {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/requirements.txt +0 -0
- {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/scripts/build_all.py +0 -0
- {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/scripts/bump_version.py +0 -0
- {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/scripts/publish_safe.py +0 -0
- {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/src/mcp_win_stdio/__main__.py +0 -0
- {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/src/mcp_win_stdio/cli.py +0 -0
- {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/src/mcp_win_stdio/core/clients.py +0 -0
- {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/src/mcp_win_stdio/core/config.py +0 -0
- {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/src/mcp_win_stdio/core/dialogs.py +0 -0
- {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/src/mcp_win_stdio/core/environment.py +0 -0
- {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/src/mcp_win_stdio/core/installer.py +0 -0
- {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/src/mcp_win_stdio/core/updater.py +0 -0
- {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/src/mcp_win_stdio/core/wizard.py +0 -0
- {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/src/mcp_win_stdio/core/workflows.py +0 -0
- {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/src/mcp_win_stdio/server/__main__.py +0 -0
- {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/src/mcp_win_stdio/server/cli.py +0 -0
- {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/src/mcp_win_stdio/server/config.py +0 -0
- {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/src/mcp_win_stdio/server/discovery.py +0 -0
- {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/src/mcp_win_stdio/server/guide.py +0 -0
- {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/src/mcp_win_stdio/server/process_pool.py +0 -0
- {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/tests/conftest.py +0 -0
- {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/tests/test_audit.py +0 -0
- {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/tests/test_db_claude_config.py +0 -0
- {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/tests/test_git.py +0 -0
- {mcp_win_stdio-0.3.2 → mcp_win_stdio-0.3.3}/tests/test_new_tools.py +0 -0
- {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.
|
|
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.
|
|
25
|
-
Requires-Dist: mcp-win-stdio-db>=0.3.
|
|
26
|
-
Requires-Dist: mcp-win-stdio-excel-db>=0.3.
|
|
27
|
-
Requires-Dist: mcp-win-stdio-excel>=0.3.
|
|
28
|
-
Requires-Dist: mcp-win-stdio-explorer>=0.3.
|
|
29
|
-
Requires-Dist: mcp-win-stdio-git>=0.3.
|
|
30
|
-
Requires-Dist: mcp-win-stdio-rag>=0.3.
|
|
31
|
-
Requires-Dist: mcp-win-stdio-ssh>=0.3.
|
|
32
|
-
Requires-Dist: mcp-win-stdio-tsc>=0.3.
|
|
33
|
-
Requires-Dist: mcp-win-stdio-word>=0.3.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
32
|
+
"mcp-win-stdio>=0.3.3",
|
|
33
33
|
"mcp>=1.2.0",
|
|
34
34
|
"networkx>=3.2.1",
|
|
35
35
|
]
|
|
@@ -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
|