Instructions is a comprehensive documentation and skills management system designed to provide AI agents with a reliable, locally-hosted "source of truth" for technical libraries, coding skills, and best practices. It ensures AI responses are based on up-to-date and accurate information through a powerful Retrieval-Augmented Generation (RAG) pipeline.
zilliz/semantic-highlight-bilingual-v1 to identify the most relevant sentences in documentsSpecialized endpoints designed for LLM agents:
/mcp/search-docs) - Semantic search with highlighting/mcp/llms-txt/{library}) - Direct access to llms.txt files/mcp/skill-doc) - Platform-specific skill creation guides/mcp/search-skills) - Search existing skill implementationsThe system provides access to three main repositories:
llm-docs: Library documentation (txt, html, md, rst)
skills-docs: Platform guidelines
skills-repos: Skill implementations
The project consists of two main components:
Documentation Synchronization Pipeline (fetch_docs.py): A Python script that fetches documentation from various sources (websites and GitHub repositories) as configured in libraries.json. The content is processed and stored locally in the llm-docs/ directory.
RAG Server (mcp_server/): A FastAPI web server that provides a semantic search API over the downloaded documentation. It uses sentence-transformers to generate embeddings for the text and lancedb as a vector store for efficient searching. The server builds the vector store for a library on-demand when it's first queried.
Follow these steps to set up and run the instructions system.
uv: This project uses uv for Python environment and package management. Ensure it is installed on your system.git clone <repository-url>
cd instructions
Before running the server, you need to download the documentation for the libraries defined in libraries.json.
# This script reads libraries.json and populates the llm-docs/ directory
uv run fetch_docs.py
The RAG server has its own set of Python dependencies.
# Navigate to the server directory
cd mcp_server
# Install dependencies using uv
uv pip install -r requirements.txt
The server must be run from the project's root directory (instructions) for the Python module paths to resolve correctly.
# Return to the project root directory
cd ..
# Run the server using uvicorn
uvicorn mcp_server.main:app --reload --host 0.0.0.0 --port 8000
The API will be available at http://0.0.0.0:8000. You can access the interactive API documentation (Swagger UI) at http://0.0.0.0:8000/docs.
You can build and run the MCP server as a Podman container. Two options are provided: a direct podman workflow or using podman-compose.
# From project root (where the Containerfile lives)
podman build -t instructions-mcp-server .
# Mount local folders to preserve docs, vector store and DB
podman run -p 8000:8000 \
-v "$(pwd)/llm-docs:/app/llm-docs:Z" \
-v "$(pwd)/mcp_server/.vector_store:/app/mcp_server/.vector_store:Z" \
-v "$(pwd)/mcp_server/libraries.db:/app/mcp_server/libraries.db:Z" \
-e FETCH_DOCS=false \
--name instructions-mcp instructions-mcp-server
Set FETCH_DOCS=true to fetch documentation during startup (may take a long time and is not recommended for production containers).
# Start services defined in podman-compose.yml
podman-compose -f podman-compose.yml up --build -d
This will build the images, create volumes to persist llm-docs, .vector_store and libraries.db, and expose the API on port 8000.
An init service is provided that runs merge_sources.py and fetch_docs.py to populate libraries.json and the llm-docs/ folder. The init service writes directly to the project folder, so its artifacts (updated libraries.json and llm-docs/) are available to the mcp_server service.
To run the init step manually (recommended):
# One-off: run init to populate llm-docs and libraries.json (writes to host-mounted files)
podman-compose -f podman-compose.yml run --rm init
Security & unprivileged init container
The init container runs as an unprivileged user (UID 10001) to reduce risk during network fetches and writes. To minimize required host permissions, the init service mounts only ./llm-docs and ./libraries.json (not the full project root), so only those two paths must be writable by the init user.
On the host, ensure llm-docs and libraries.json are writable by UID 10001 (one of the following):
sudo chown -R 10001:10001 ./llm-docs && sudo chown 10001:10001 ./libraries.jsonsudo setfacl -R -m u:10001:rwx ./llm-docs && sudo setfacl -m u:10001:rw ./libraries.jsonThe init image will refuse to run if it cannot write to /work/llm-docs or /work/libraries.json to avoid silent failures and accidental root-owned files.
If the init container fails with a permissions error, apply one of the above host fixes and re-run the init job (e.g., ./run.sh init).
Security note: mount llm-docs with noexec on the host
llm-docs with noexec so files written there cannot be executed directly on the host. The provided podman-compose.yml uses bind options to request noexec on the llm-docs and the server enforces non-executable permissions on files at startup.podman-compose.yml):volumes:
- type: bind
source: ./llm-docs
target: /app/llm-docs
bind:
options: ["noexec", "rw", "Z"]
If your host filesystem or environment doesn't support noexec bind flags, you can manually ensure safety by running:
# On the host
chmod -R a-x llm-docs
mount -o remount,noexec $(pwd)/llm-docs
If mount -o remount,noexec fails due to your filesystem, keep the chmod -R a-x protection in place and consider using a separate filesystem/volume for llm-docs that supports strict mount options.
If you start podman-compose up without running the init first, the compose file specifies depends_on: init for mcp_server, which will ensure the init container is created. However, a recommended approach is to run the init container explicitly to observe logs and progress.
If you have an NVIDIA GPU and a compatible runtime, an optional mcp_server_gpu service is provided that uses a CUDA-enabled PyTorch runtime for faster embedding and model inference.
Notes & usage:
mcp_server_gpu image is built from a PyTorch CUDA runtime (see Containerfile.gpu). It exposes the API on port 8001 by default to keep it separate from the CPU service.GTX 1050Ti (compute capability 6.1)
Containerfile.gpu uses a PyTorch image built with CUDA 11.7 (pytorch/pytorch:2.1.0-cuda11.7-cudnn8-runtime), which is compatible with that GPU.Run the GPU variant (example):
# Build GPU image and start the GPU-enabled service
podman-compose -f podman-compose.yml up --build mcp_server_gpu -d
# Note: You may need to pass --device or configure hooks to expose the GPU devices depending on your Podman setup.
Tip: For production environments, consider running indexing/embedding as a dedicated worker using a GPU-enabled image and keeping the API server separate for better reliability and scale.
The server provides multiple APIs for different use cases.
curl -X POST "http://0.0.0.0:8000/search" \
-H "Content-Type: application/json" \
-d '{
"library_name": "nextjs",
"search_term": "how to handle client side rendering",
"limit": 5,
"highlight": true
}'
curl -X POST "http://0.0.0.0:8000/mcp/search-docs" \
-H "Content-Type: application/json" \
-d '{
"library_name": "fastapi",
"query": "How to implement dependency injection?",
"limit": 3,
"highlight": true
}'
curl "http://0.0.0.0:8000/mcp/llms-txt/nextjs"
curl -X POST "http://0.0.0.0:8000/mcp/skill-doc" \
-H "Content-Type: application/json" \
-d '{
"platform": "claude-code"
}'
curl -X POST "http://0.0.0.0:8000/mcp/search-skills" \
-H "Content-Type: application/json" \
-d '{
"query": "API testing with authentication",
"platform": "copilot",
"limit": 10
}'
# List all WAF rules
curl "http://0.0.0.0:8000/admin/waf/rules"
# Add custom rule
curl -X POST "http://0.0.0.0:8000/admin/waf/rules" \
-H "Content-Type: application/json" \
-d '{
"rule_id": "custom_block",
"rule_type": "PATTERN",
"action": "DENY",
"target": "query",
"pattern": "sensitive_pattern",
"priority": 50,
"description": "Block sensitive patterns"
}'
# Disable a rule
curl -X POST "http://0.0.0.0:8000/admin/waf/rules/custom_block/disable"
# Get WAF statistics
curl "http://0.0.0.0:8000/admin/waf/stats"
# Rebuild library index
curl -X POST "http://0.0.0.0:8000/admin/resync" \
-H "Content-Type: application/json" \
-d '{
"library_name": "nextjs",
"rebuild_chunks": true,
"wait": true
}'
# Sync filesystem
curl -X POST "http://0.0.0.0:8000/admin/sync_fs" \
-H "Content-Type: application/json" \
-d '{"wait": false}'
Visit http://0.0.0.0:8000/docs for the full Swagger UI with all endpoints, schemas, and try-it-out functionality.
uv for all Python-related tasks (execution, package management).fetch_docs.py: Handles all data ingestion.mcp_server/: Contains the API and RAG logic.
main.py: Defines API routes and the server.rag.py: Encapsulates all logic for document processing, embedding, and searching.Special thanks to the following sources for providing public lists of llms.txt documentation endpoints:
These resources make it possible to discover and aggregate LLM-friendly documentation across the web.
Instructions is a comprehensive documentation and skills management system designed to provide AI agents with a reliable, locally-hosted "source of truth" for technical libraries, coding skills, and best practices. It ensures AI responses are based on up-to-date and accurate information through a powerful Retrieval-Augmented Generation (RAG) pipeline.
zilliz/semantic-highlight-bilingual-v1 to identify the most relevant sentences in documentsSpecialized endpoints designed for LLM agents:
/mcp/search-docs) - Semantic search with highlighting/mcp/llms-txt/{library}) - Direct access to llms.txt files/mcp/skill-doc) - Platform-specific skill creation guides/mcp/search-skills) - Search existing skill implementationsThe system provides access to three main repositories:
llm-docs: Library documentation (txt, html, md, rst)
skills-docs: Platform guidelines
skills-repos: Skill implementations
The project consists of two main components:
Documentation Synchronization Pipeline (fetch_docs.py): A Python script that fetches documentation from various sources (websites and GitHub repositories) as configured in libraries.json. The content is processed and stored locally in the llm-docs/ directory.
RAG Server (mcp_server/): A FastAPI web server that provides a semantic search API over the downloaded documentation. It uses sentence-transformers to generate embeddings for the text and lancedb as a vector store for efficient searching. The server builds the vector store for a library on-demand when it's first queried.
Follow these steps to set up and run the instructions system.
uv: This project uses uv for Python environment and package management. Ensure it is installed on your system.git clone <repository-url>
cd instructions
Before running the server, you need to download the documentation for the libraries defined in libraries.json.
# This script reads libraries.json and populates the llm-docs/ directory
uv run fetch_docs.py
The RAG server has its own set of Python dependencies.
# Navigate to the server directory
cd mcp_server
# Install dependencies using uv
uv pip install -r requirements.txt
The server must be run from the project's root directory (instructions) for the Python module paths to resolve correctly.
# Return to the project root directory
cd ..
# Run the server using uvicorn
uvicorn mcp_server.main:app --reload --host 0.0.0.0 --port 8000
The API will be available at http://0.0.0.0:8000. You can access the interactive API documentation (Swagger UI) at http://0.0.0.0:8000/docs.
You can build and run the MCP server as a Podman container. Two options are provided: a direct podman workflow or using podman-compose.
# From project root (where the Containerfile lives)
podman build -t instructions-mcp-server .
# Mount local folders to preserve docs, vector store and DB
podman run -p 8000:8000 \
-v "$(pwd)/llm-docs:/app/llm-docs:Z" \
-v "$(pwd)/mcp_server/.vector_store:/app/mcp_server/.vector_store:Z" \
-v "$(pwd)/mcp_server/libraries.db:/app/mcp_server/libraries.db:Z" \
-e FETCH_DOCS=false \
--name instructions-mcp instructions-mcp-server
Set FETCH_DOCS=true to fetch documentation during startup (may take a long time and is not recommended for production containers).
# Start services defined in podman-compose.yml
podman-compose -f podman-compose.yml up --build -d
This will build the images, create volumes to persist llm-docs, .vector_store and libraries.db, and expose the API on port 8000.
An init service is provided that runs merge_sources.py and fetch_docs.py to populate libraries.json and the llm-docs/ folder. The init service writes directly to the project folder, so its artifacts (updated libraries.json and llm-docs/) are available to the mcp_server service.
To run the init step manually (recommended):
# One-off: run init to populate llm-docs and libraries.json (writes to host-mounted files)
podman-compose -f podman-compose.yml run --rm init
Security & unprivileged init container
The init container runs as an unprivileged user (UID 10001) to reduce risk during network fetches and writes. To minimize required host permissions, the init service mounts only ./llm-docs and ./libraries.json (not the full project root), so only those two paths must be writable by the init user.
On the host, ensure llm-docs and libraries.json are writable by UID 10001 (one of the following):
sudo chown -R 10001:10001 ./llm-docs && sudo chown 10001:10001 ./libraries.jsonsudo setfacl -R -m u:10001:rwx ./llm-docs && sudo setfacl -m u:10001:rw ./libraries.jsonThe init image will refuse to run if it cannot write to /work/llm-docs or /work/libraries.json to avoid silent failures and accidental root-owned files.
If the init container fails with a permissions error, apply one of the above host fixes and re-run the init job (e.g., ./run.sh init).
Security note: mount llm-docs with noexec on the host
llm-docs with noexec so files written there cannot be executed directly on the host. The provided podman-compose.yml uses bind options to request noexec on the llm-docs and the server enforces non-executable permissions on files at startup.podman-compose.yml):volumes:
- type: bind
source: ./llm-docs
target: /app/llm-docs
bind:
options: ["noexec", "rw", "Z"]
If your host filesystem or environment doesn't support noexec bind flags, you can manually ensure safety by running:
# On the host
chmod -R a-x llm-docs
mount -o remount,noexec $(pwd)/llm-docs
If mount -o remount,noexec fails due to your filesystem, keep the chmod -R a-x protection in place and consider using a separate filesystem/volume for llm-docs that supports strict mount options.
If you start podman-compose up without running the init first, the compose file specifies depends_on: init for mcp_server, which will ensure the init container is created. However, a recommended approach is to run the init container explicitly to observe logs and progress.
If you have an NVIDIA GPU and a compatible runtime, an optional mcp_server_gpu service is provided that uses a CUDA-enabled PyTorch runtime for faster embedding and model inference.
Notes & usage:
mcp_server_gpu image is built from a PyTorch CUDA runtime (see Containerfile.gpu). It exposes the API on port 8001 by default to keep it separate from the CPU service.GTX 1050Ti (compute capability 6.1)
Containerfile.gpu uses a PyTorch image built with CUDA 11.7 (pytorch/pytorch:2.1.0-cuda11.7-cudnn8-runtime), which is compatible with that GPU.Run the GPU variant (example):
# Build GPU image and start the GPU-enabled service
podman-compose -f podman-compose.yml up --build mcp_server_gpu -d
# Note: You may need to pass --device or configure hooks to expose the GPU devices depending on your Podman setup.
Tip: For production environments, consider running indexing/embedding as a dedicated worker using a GPU-enabled image and keeping the API server separate for better reliability and scale.
The server provides multiple APIs for different use cases.
curl -X POST "http://0.0.0.0:8000/search" \
-H "Content-Type: application/json" \
-d '{
"library_name": "nextjs",
"search_term": "how to handle client side rendering",
"limit": 5,
"highlight": true
}'
curl -X POST "http://0.0.0.0:8000/mcp/search-docs" \
-H "Content-Type: application/json" \
-d '{
"library_name": "fastapi",
"query": "How to implement dependency injection?",
"limit": 3,
"highlight": true
}'
curl "http://0.0.0.0:8000/mcp/llms-txt/nextjs"
curl -X POST "http://0.0.0.0:8000/mcp/skill-doc" \
-H "Content-Type: application/json" \
-d '{
"platform": "claude-code"
}'
curl -X POST "http://0.0.0.0:8000/mcp/search-skills" \
-H "Content-Type: application/json" \
-d '{
"query": "API testing with authentication",
"platform": "copilot",
"limit": 10
}'
# List all WAF rules
curl "http://0.0.0.0:8000/admin/waf/rules"
# Add custom rule
curl -X POST "http://0.0.0.0:8000/admin/waf/rules" \
-H "Content-Type: application/json" \
-d '{
"rule_id": "custom_block",
"rule_type": "PATTERN",
"action": "DENY",
"target": "query",
"pattern": "sensitive_pattern",
"priority": 50,
"description": "Block sensitive patterns"
}'
# Disable a rule
curl -X POST "http://0.0.0.0:8000/admin/waf/rules/custom_block/disable"
# Get WAF statistics
curl "http://0.0.0.0:8000/admin/waf/stats"
# Rebuild library index
curl -X POST "http://0.0.0.0:8000/admin/resync" \
-H "Content-Type: application/json" \
-d '{
"library_name": "nextjs",
"rebuild_chunks": true,
"wait": true
}'
# Sync filesystem
curl -X POST "http://0.0.0.0:8000/admin/sync_fs" \
-H "Content-Type: application/json" \
-d '{"wait": false}'
Visit http://0.0.0.0:8000/docs for the full Swagger UI with all endpoints, schemas, and try-it-out functionality.
uv for all Python-related tasks (execution, package management).fetch_docs.py: Handles all data ingestion.mcp_server/: Contains the API and RAG logic.
main.py: Defines API routes and the server.rag.py: Encapsulates all logic for document processing, embedding, and searching.Special thanks to the following sources for providing public lists of llms.txt documentation endpoints:
These resources make it possible to discover and aggregate LLM-friendly documentation across the web.