Command Palette

Search for a command to run...

UnylyUnyly
Весь каталог

Cpp Semantic Graph

БесплатноНе проверен

Builds a semantic knowledge graph of C++ code and exposes 9 MCP tools for AI assistants to search classes, functions, inheritance, callers, callees, overrides,

GitHubEmbed

Описание

Builds a semantic knowledge graph of C++ code and exposes 9 MCP tools for AI assistants to search classes, functions, inheritance, callers, callees, overrides, and more.

README

A C++ semantic knowledge graph — let AI understand your C++ codebase precisely

中文文档

Builds a semantic knowledge graph of a C++ codebase for AI coding assistants (Claude Code, Cursor, Windsurf, etc.) and exposes it through 9 query tools over the MCP protocol. The AI can then search class definitions, look up inheritance, trace call chains, and analyze the blast radius of a change — no file hopping, no grep.

Why this project exists: clang, not tree-sitter

Most general-purpose code-graph tooling parses source with tree-sitter — a fast, incremental syntax parser. That choice is fine for many languages, but it is the wrong tool for a C++ semantic graph.

The distinction is syntax vs. semantics:

  • A syntax parser answers "do these tokens form a valid C++ program, and what does the tree look like?" — it does not resolve what a name refers to.
  • Semantic analysis (what a compiler front-end does) answers "which entity does this symbol denote, what is its type, and how does it relate to others?" — this requires type resolution and name lookup.

C++ is a language where a great deal of information lives in the type system, not on the page. Consider:

namespace nv {
struct Base { virtual void PerformUpgrade() = 0; };
template<typename T> struct Middle : virtual Base {     // virtual inheritance + template
    void PerformUpgrade() override {}
};
struct SoCUpdate : Middle<SoC>, NonCopyable, public Logger {};   // multiple inheritance
}

A syntax parser (tree-sitter) can see that SoCUpdate is a class followed by three base-specifier text fragments. It cannot determine:

  • That Middle<SoC> is the specialization nv::Middle<SoC> — requires template instantiation.
  • Which namespace Logger lives in — requires name lookup.
  • That the root of the chain is virtual Base — requires expanding the template and following the referenced declaration.
  • Which base-class method PerformUpgrade() override actually overrides — requires cross-class virtual-function matching.

These all require type resolution, which is exactly what a compiler front-end (clang) provides and what a syntax parser structurally cannot. This is not tree-sitter being "bad" — it is intentionally type-free, single-file, and fast, designed for syntax highlighting and code folding. Using it to build a C++ semantic graph is simply the wrong tool for the job.

This project uses clang/libclang's semantic AST instead. The difference is visible in the parser:

  • base_spec.referenced — resolves a base class to its real declaration cursor across files and namespaces (parser/ast_visitor.py).
  • clang_isVirtualBase — detects virtual inheritance, the C++-specific diamond-inheritance semantics that syntax trees have no notion of.
  • cursor.is_virtual_method() / is_pure_virtual_method() — virtual-function semantics.
  • access_specifier — distinguishes public/protected/private inheritance.
  • Cross-TU override matching — base and derived classes often live in different files; clang's referenced cursor links them, which a per-file syntax parse cannot.

Why do you need it?

Common pain points when an AI assistant tries to understand C++ code:

Pain point cpp-semantic-graph's answer
"Where is this class defined?" cpp_search_class("SocUpdate") → namespace + file location
"Who calls this function?" cpp_get_callers("GetSocBootChain") → all callers
"What does changing this header affect?" Incremental update recursively walks include deps, re-parses only affected TUs
"Which overrides exist for this virtual?" cpp_get_overrides("PerformUpgrade", "BasePeriUpdate") → all implementations
"What does this module's architecture look like?" cpp_traverse_graph("SocUpdate") → multi-hop traversal

✨ Core features

  • 9 MCP tools: class search, function search, inheritance, call chains (caller/callee), overrides, file symbols, multi-hop traversal, doc search
  • Incremental updates: based on the include-dependency graph, a single .cpp change refreshes in seconds (16× faster than a full rebuild)
  • Doc fusion: bidirectional linking between project docs and code; searching docs auto-locates related code
  • Plug-and-play: one YAML config + compile_commands.json to start; the MCP server auto-registers with AI tools
  • Project-agnostic: no project-specific hardcoding in the schema or tool definitions — portable to any C++ project

🚀 Quick start

Prerequisites

  • Python 3.10+
  • libclang (matching your LLVM version)
  • compile_commands.json (generated by CMake/Bear)

1. Install

git clone https://github.com/FS-Yuao/cpp-semantic-graph.git
cd cpp-semantic-graph

# 一键创建 venv 并安装核心依赖 (推荐)
./setup_env.sh
#   默认在项目内创建 .venv;可选:
#   ./setup_env.sh /path/to/venv   指定 venv 路径
#   ./setup_env.sh --with-docs     同时装 doc embedding 依赖 (含 torch,体积大)

# 或手动创建:
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt

前置: 系统 libclang (匹配 LLVM 版本;Ubuntu: apt install libclang-18-dev)。 clang bindings 版本须与系统 libclang 一致 (本机 libclang-18 对应 clang>=18,<19,换 LLVM 版本时同步改 requirements.txt)。

2. Prepare compile_commands.json

If your project builds with CMake:

cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON ...

Without CMake, use Bear to intercept build commands:

bear -- make

3. Write the config file

Create cpp_semantic_graph.yaml (the only file you need to edit):

project:
  name: "your_project"
  compile_commands: "/path/to/your/project/compile_commands.json"

# Source scope — decides what counts as "project code"
source_paths:
  - "src"
  - "include"

# Generated-code paths (e.g. ARA COM src-gen, protobuf)
generated_paths:
  - "src-gen"

# Paths to ignore entirely
exclude_paths:
  - "thirdparty"
  - "build"
  - "test/mock"

# libclang path (adjust for your system)
libclang_path: "/usr/lib/llvm-18/lib/libclang.so.1"

parse_options:
  skip_function_bodies: false  # must be false to extract call relations
  max_workers: 4               # parallel parse processes

4. Full parse

⚠️ Important: stop the MCP server before a full parse, or the DB connection it holds will cause write failures (disk I/O error). Stop with pkill -f "run_server.py", or disable the MCP server in Claude Code settings and restart.

# 1. Stop the MCP server (required!)
pkill -f "run_server.py"

# 2. Full parse
python3 -m cpp_semantic_graph full-parse \
  --config cpp_semantic_graph.yaml \
  --db semantic_graph_full.db

# 3. Restart the MCP server (after parsing finishes)

After parsing, the database contains:

  • Nodes: classes, structs, functions (with signature, namespace, file location)
  • Edges: inheritance, calls, overrides, belongs_to, type aliases (type_alias), using-declarations (using_decl), friends (friend_of), etc. (the instantiates template-instantiation edge is not yet enabled due to libclang AST shape — see Complex scenarios)
  • Include dependencies: the include graph between translation units
  • Doc associations: doc sections ↔ code entities (optional)

5. Start the MCP server

# Set the database path
export CPP_GRAPH_DB=/path/to/semantic_graph_full.db

# Start the MCP server (stdio transport)
python3 -m cpp_semantic_graph.mcp_server.server

6. Register with AI tools

Claude Code

Edit ~/.claude.json, add under mcpServers:

{
  "mcpServers": {
    "cpp-semantic-graph": {
      "type": "stdio",
      "command": "python3",
      "args": ["/absolute/path/to/cpp-semantic-graph/mcp_server/run_server.py"],
      "env": {
        "CPP_GRAPH_DB": "/absolute/path/to/semantic_graph_full.db"
      }
    }
  }
}

Cursor

Edit .cursor/mcp.json:

{
  "mcpServers": {
    "cpp-semantic-graph": {
      "command": "python3",
      "args": ["/absolute/path/to/cpp-semantic-graph/mcp_server/run_server.py"],
      "env": {
        "CPP_GRAPH_DB": "/absolute/path/to/semantic_graph_full.db"
      }
    }
  }
}

Windsurf / other MCP clients

Configuration is similar; refer to each tool's MCP docs. The core parameters:

  • command: python3
  • args: ["/path/to/mcp_server/run_server.py"]
  • env.CPP_GRAPH_DB: absolute path to the database

💡 Tip: the CPP_GRAPH_PROJECT env var sets the project name (used in MCP instructions); if unset it is inferred from the DB path.


🛠️ 9 MCP tools

# Tool Purpose Typical scenario
1 cpp_search_class Search class definitions by name "Where is SocUpdate defined?"
2 cpp_search_function Search function definitions by name "What's the signature of PerformUpgrade?"
3 cpp_get_inheritance Query a class's inheritance "Which classes derive from BasePeriUpdate?"
4 cpp_get_callers Who calls a given function "Who calls GetSocBootChain?"
5 cpp_get_callees What a given function calls "What does PerformUpgrade call internally?"
6 cpp_get_overrides All overrides of a virtual function "Which overrides exist for PerformUpgrade?"
7 cpp_get_file_symbols All symbols in a file "What's in soc_update.cpp?"
8 cpp_traverse_graph Multi-hop graph traversal "What does changing SocUpdate affect?"
9 cpp_search_docs Search project docs "The OTA upgrade-flow design doc"

Tool details

cpp_search_class(name, exact=False)

Search C++ class definitions by name. Supports fuzzy matching.

cpp_search_class("SocUpdate")
-> ## Search results: class "SocUpdate" (1)
  ### update::SocUpdate
  - File: soc_update.h:15-120

cpp_search_function(name, class_name="")

Search function definitions by name. Optionally restrict by owning class.

cpp_search_function("PerformUpgrade", class_name="SocUpdate")
-> ## Search results: function "PerformUpgrade" (1)
  ### SocUpdate::PerformUpgrade [virtual, override]
  - Signature: void PerformUpgrade() override
  - File: soc_update.cpp:45

cpp_get_inheritance(class_name, direction="down", depth=1)

Query inheritance. direction="down" for subclasses, "up" for base classes. depth=-1 recurses fully.

cpp_get_inheritance("BasePeriUpdate", direction="down", depth=-1)
-> ## Subclasses of BasePeriUpdate (4)
  - update::SocUpdate --public--> BasePeriUpdate
  - update::McuUpdate --public--> BasePeriUpdate
  - ...

cpp_get_callers(function_name, class_name="")

Who calls a given function (impact analysis).

cpp_get_callers("GetSocBootChain")
-> ## Code that calls "GetSocBootChain" (3)
  ### OtaManager::CheckBootChain
  - File: ota_manager.cpp:128
  - Call type: calls_direct

cpp_get_callees(function_name, class_name="")

What a given function calls (call-chain analysis).

cpp_get_callees("PerformUpgrade", class_name="SocUpdate")
-> ## Code called by "PerformUpgrade" (8)
  ...

cpp_get_overrides(function_name, class_name)

All override implementations of a virtual function. class_name is the base class that declares the virtual (required).

cpp_get_overrides("PerformUpgrade", class_name="BasePeriUpdate")
-> ## Overrides of "PerformUpgrade" (4)
  ### update::SocUpdate::PerformUpgrade
  - Signature: void PerformUpgrade() override
  - File: soc_update.cpp:45
  - Overrides base: BasePeriUpdate

cpp_get_file_symbols(file_path)

All class and function symbols in a file. file_path supports partial matching.

cpp_get_file_symbols("soc_update.h")
-> ## File symbols: soc_update.h (12 total)
  ### Classes/structs (2)
    1. ### [class] update::SocUpdate
  ### Functions (10)
    ...

cpp_traverse_graph(start, relation_types=None, direction="outgoing", depth=3, max_results=50)

Multi-hop graph traversal — the most flexible query. Walks related nodes along specified relation types.

Common relation types: inherits_public, inherits_protected, overrides, belongs_to, calls_direct, calls_virtual, calls_callback, doc_describes_code, code_refers_to_doc

cpp_traverse_graph("SocUpdate", depth=2, max_results=30)
-> ## Traversal results: from "SocUpdate" (18 nodes)
  Depth: 2, edges traversed: 22
  ### Related nodes
    - [class] update::SocUpdate (soc_update.h)
    - [class] update::BasePeriUpdate (base_peri_update.h)
    - [function] update::SocUpdate::PerformUpgrade (soc_update.cpp)
    ...

cpp_search_docs(keyword, tag="", max_results=10, min_confidence=0.0)

Search project docs, returns doc sections + associated code.

cpp_search_docs("升级", tag="架构设计")
-> ## Doc search: "升级" (3 results)
  ### OTA complete upgrade flow
  - File: docs/OTA_flow/OTA_COMPLETE_FLOW.md
  - Word count: 2450
  - Tags: 架构设计, OTA

  Associated code:
    - [class] BasePeriUpdate confidence=0.92
    - [class] SocUpdate confidence=0.88

📖 CLI usage

Besides MCP tools, a CLI is provided for direct queries:

# Search a class
python3 -m cpp_semantic_graph search-class "SocUpdate"

# Query inheritance
python3 -m cpp_semantic_graph inheritance "BasePeriUpdate" --direction down --depth -1

# Search a function
python3 -m cpp_semantic_graph search-func "PerformUpgrade"

# File symbols
python3 -m cpp_semantic_graph file-symbols "soc_update.cpp"

# Include dependencies
python3 -m cpp_semantic_graph include "base_peri_update.h" --mode all

# DB stats
python3 -m cpp_semantic_graph stats

Incremental update

After a code change, the incremental update re-parses only affected translation units:

# Based on git diff (default HEAD~1)
python3 -m cpp_semantic_graph incremental --base HEAD~1

# Specify files manually
python3 -m cpp_semantic_graph incremental --files soc_update.cpp,base_peri_update.h

# Detect only, don't execute
python3 -m cpp_semantic_graph incremental --files soc_update.cpp --dry-run

# Skip doc-association rebuild (faster)
python3 -m cpp_semantic_graph incremental --files soc_update.cpp --skip-associations

📐 Architecture

┌─────────────────────────────────────────────────┐
│                  AI tool layer                   │
│  Claude Code / Cursor / Windsurf / other MCP     │
└────────────────────┬────────────────────────────┘
                     │ MCP protocol (stdio)
┌────────────────────▼────────────────────────────┐
│              MCP Server (9 tools)                │
│  FastMCP + Lazy-init DB connection + Markdown    │
└────────────────────┬────────────────────────────┘
                     │ Python API
┌────────────────────▼────────────────────────────┐
│                Query layer (query/)              │
│  GraphQuery │ CallQuery │ PolymorphismQuery      │
│  TraverseQuery │ DocQuery │ IncludeQuery         │
└────────────────────┬────────────────────────────┘
                     │
┌────────────────────▼────────────────────────────┐
│                Data layer (db/)                  │
│  SQLite + 9 indexes + CASCADE constraints        │
│  node │ edge │ include_dep │ parse_status        │
└─────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────┐
│              Parser layer (parser/)              │
│  AST Visitor (libclang) │ CompileDB │ Config     │
│  ChangeDetector │ ImpactAnalyzer                 │
└─────────────────────────────────────────────────┘

Core data model

Nodes (node table): classes, structs, functions — with namespace, file location, signature, etc.

Edges (edge table): relations between nodes; direction convention:

  • inherits: from=subclass → to=base class
  • calls: from=caller → to=callee
  • belongs_to: from=function → to=owning class
  • overrides: from=derived function → to=base function

include_dep table: include dependencies between translation units — the core basis for incremental updates.


🧩 Complex scenarios (template/alias/friend)

Feature Relation edge Status Notes
Type alias using Alias = T type_alias ✅ Enabled Alias node stored + edge links to target; when the target comes from an external library the edge may be dropped as dangling, but the alias node keeps target_type metadata
using-declaration using B::func using_decl ✅ Enabled Subclass function → base function; this project's source has no ordinary using-declarations, only 1 literal operator not extracted
Friend friend class F friend_of ✅ Enabled friend → host class; this project's source has no friend declarations, 0 edges as expected
Template instantiation instantiates ⏸️ Not yet enabled libclang does not produce a standalone CLASS_DECL node for template specializations (the specialization name only appears in the spelling of CONSTRUCTOR/TYPE_REF), so walk_preorder finds no class declaration containing < and extraction yields nothing. The extractor code is retained, to be enabled once we switch to LibTooling or rebuild from TYPE_REF

This area previously had "extractors written but not wired into the pipeline" (dead code). AliasExtractor/FriendExtractor are now integrated into SemanticExtractor.parse(), and output was verified against clangd.


🔄 Incremental-update mechanism

1. ChangeDetector   ──-> detect file changes (git diff / manual)
2. ImpactAnalyzer    ──-> analyze blast radius (.h -> recursive includers)
3. Delete stale data ──-> delete out-edges (not shared nodes) + include_dep + parse_status
4. Re-parse          ──-> SemanticExtractor.parse() × affected TUs
5. Upsert new data   ──-> node update / edge dedup
6. Cleanup remnants  ──-> delete functions/classes no longer in the file
7. Rebuild doc links ──-> content_scan (optional embedding)

Core deletion strategy: only out-edges (edges whose from_id is in that file) are deleted; in-edges are kept; nodes are updated via upsert. This ensures classes/functions shared in headers are never mistakenly deleted.

Scenario Affected TUs Update time
Single .cpp change 1 ~11s
Single .h change recursive includers depends on TU count (7 TUs ~76s)
Idempotency (second run) unchanged edge count stable ✅

Note: the git-diff auto-detect mode (--base HEAD~1) may not work under Android repo-tool-managed repos (_ensure_repo_root finds the repo top-level .git rather than the sub-repo's). Using --files to specify changed files manually is more reliable.


📚 Doc fusion

Bidirectionally links project docs (Markdown) with code entities, so searching docs auto-locates related code.

This project has doc fusion configured: 58 docs → 546 sections → 1,756 association edges (doc_describes_code + code_refers_to_doc). cpp_search_docs works out of the box.

Configuration

Create config/doc_config.yaml:

doc_dirs:
  - "docs/"

exclude_patterns:
  - "*.html"
  - "*/build/*"

# Auto-tag by directory
tag_rules:
  - path_pattern: "architecture/**"
    tags: ["架构设计"]
  - path_pattern: "api/**"
    tags: ["接口规约"]

section_split:
  min_level: 2          # split on ##
  min_word_count: 20    # sections under 20 words are merged

# Manual precise links (no intrusion into doc text)
manual_links:
  - doc: "architecture/OTA_FLOW.md"
    heading: "升级流程"
    code:
      - "BasePeriUpdate"
      - "SocUpdate"
      - "PerformUpgrade"

Embedding association (optional)

After installing sentence-transformers, docs and code can be auto-associated by semantic similarity:

pip install sentence-transformers

Defaults to all-MiniLM-L6-v2. For Chinese projects, bge-small-zh-v1.5 or multilingual-e5-small is recommended.


🧪 Validation

After a full parse, you can run correctness validation (cross-checked against clangd):

python3 -m cpp_semantic_graph full-parse \
  --config cpp_semantic_graph.yaml \
  --validate \
  --baseline validation/clangd_baseline.json

📋 Dependencies

Core (required)

Package Version Purpose
clang ≥18 libclang Python bindings, AST parsing
PyYAML ≥6.0 Config file parsing
mcp ≥1.0 MCP protocol implementation (FastMCP)

Doc fusion (optional)

Package Version Purpose
sentence-transformers ≥2.0 doc-code semantic association
torch ≥2.0 sentence-transformers dependency

Install

# Core
pip install clang PyYAML mcp

# Doc fusion (optional)
pip install sentence-transformers

Or with requirements files:

pip install -r requirements.txt        # core
pip install -r requirements-docs.txt   # doc fusion (optional)

🗂️ Project structure

cpp_semantic_graph/
├── __init__.py                  # package declaration
├── __main__.py                  # python -m entry point
├── cli.py                       # CLI tools (search/inheritance/incremental/...)
├── pipeline.py                  # full-parse pipeline
├── incremental_updater.py       # incremental-update orchestrator
│
├── parser/                      # parser layer
│   ├── ast_visitor.py           #   AST extractor (libclang)
│   ├── config.py                #   project config loading
│   ├── compile_db.py            #   compile_commands.json parsing
│   ├── change_detector.py       #   file-change detection (git diff)
│   ├── impact_analyzer.py       #   blast-radius analysis
│   ├── doc_parser.py            #   doc parser
│   ├── doc_association.py       #   doc-code association
│   ├── association_ingester.py  #   association-edge ingestion
│   └── models.py                #   data models
│
├── query/                       # query layer
│   ├── graph_query.py           #   class/function/file-symbol queries
│   ├── call_query.py            #   call-relation queries
│   ├── polymorphism_query.py    #   polymorphism queries
│   ├── traverse.py              #   multi-hop traversal
│   ├── doc_query.py             #   doc-fusion queries
│   ├── include_query.py         #   include-dependency queries
│   ├── architecture_query.py    #   architecture-overview queries
│   └── fusion_query.py          #   fusion queries
│
├── db/                          # data layer
│   ├── graph_db.py              #   SQLite operations
│   ├── importer.py              #   JSON->SQLite import
│   ├── schema.sql               #   schema + indexes
│   └── relation_types.py        #   relation-type enum
│
├── mcp_server/                  # MCP server
│   ├── server.py                #   FastMCP server + 9 tools
│   └── run_server.py            #   launch script
│
├── validation/                  # correctness validation
│   ├── accuracy_validator.py    #   accuracy validator
│   └── clangd_baseline.py       #   clangd baseline
│
├── config/                      # config templates
│   ├── doc_config.yaml          #   doc-config example
│   └── template_whitelist.yaml  #   template whitelist
│
└── cpp_semantic_graph.yaml      # project config (user-authored)

❓ FAQ

Q: Which LLVM/libclang version do I need?

One matching the LLVM used to compile your project. How to check:

# clang version
clang --version

# corresponding libclang path
ls /usr/lib/llvm-*/lib/libclang.so*

Specify it via libclang_path in the config.

Q: How do I generate compile_commands.json?

  • CMake project: cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON ...
  • Make project: bear -- make
  • Ninja project: bear -- ninja
  • Bazel project: use bazel-compile-commands

Q: Are incremental updates safe?

Yes. The deletion strategy only removes out-edges (edges whose from_id is in that file), not nodes. Classes/functions shared in headers are updated via upsert and never mistakenly deleted. For extreme cases (e.g. renaming a class), a full rebuild is recommended.

Q: Which AI tools are supported?

Any AI tool that supports the MCP protocol: Claude Code, Cursor, Windsurf, Continue, etc. The MCP server uses stdio transport, the most broadly supported transport.

Q: How big is the database?

A typical C++ project (~100 translation units): about 1500 nodes / 5000 edges / 20000 include relations, SQLite file ~5-10 MB. With doc fusion, node and edge counts roughly double, DB ~5-15 MB.

Q: How does this differ from clangd?

cpp-semantic-graph clangd
Data storage offline SQLite graph real-time AST
Query scope cross-file, cross-module single file + index
Call chains full call graph + multi-hop traversal single-hop references
Doc association supported not supported
Incremental update based on include-dependency graph real-time
Best for architecture understanding, blast-radius analysis real-time editing, signature lookup

They complement each other: use clangd for day-to-day editing, cpp-semantic-graph for architecture understanding and impact analysis.


📄 License

MIT License


📋 Test reports

Report Description
TEST_REPORT.md Comprehensive test report (functionality/accuracy/efficiency/bug-fix)
TEST_CASES.md Test-case table (62 cases, 98.4% pass)
TEST_THREE_LAYERS.md Three-layer tests (question→tool call→code verification, 25 real questions)
TEST_DOC_FUSION.md Doc-fusion tests (27 cases, 89% pass)
PROJECT_EVALUATION.md Overall project evaluation (six-dimension scoring + comparison + conclusion)

from github.com/FS-Yuao/cpp-semantic-graph

Установка Cpp Semantic Graph

У этого сервера нет опубликованного пакета — он собирается из исходников. Открой репозиторий и следуй инструкции в README.

▸ github.com/FS-Yuao/cpp-semantic-graph

FAQ

Cpp Semantic Graph MCP бесплатный?

Да, Cpp Semantic Graph MCP бесплатный — установка в пару кликов через Unyly без оплаты.

Нужен ли API-ключ для Cpp Semantic Graph?

Нет, Cpp Semantic Graph работает без API-ключей и переменных окружения.

Cpp Semantic Graph — hosted или self-hosted?

Self-hosted: сервер запускается локально на твоей машине командой из раздела установки.

Как установить Cpp Semantic Graph в Claude Desktop, Claude Code или Cursor?

Открой Cpp Semantic Graph на unyly.org, выбери вкладку своего клиента (Claude Desktop, Claude Code, Cursor) и нажми Install — конфиг сгенерируется автоматически, без правки JSON.

Похожие MCP

Compare Cpp Semantic Graph with

Не уверен что выбрать?

Найди свой стек за 60 секунд

Автор?

Embed-бейдж для README

Похожее

Все в категории development