mcp-kg-skills
# MCP Knowledge Graph Skills
A Model Context Protocol (MCP) server that manages a graph of reusable Python functions, documentation, and environment variables. Claude can dynamically compose and execute scripts by importing functions from the graph.
## Features
- **Graph-Based Knowledge Management**: Organize skills, scripts, documentation, and environments in a Neo4j knowledge graph
- **Dynamic Script Composition**: Import and combine Python functions at execution time
- **Automatic Dependency Management**: PEP 723 inline script metadata with uv-powered execution
- **Secret Protection**: Automatic detection and sanitization of sensitive environment variables
- **Relationship Tracking**: Connect related skills and resources with CONTAINS and RELATE_TO relationships
- **Flexible Querying**: Explore the knowledge graph using read-only Cypher queries
## Quick Start
Get started in 3 steps:
```bash
# 1. Install uv (if not already installed)
curl -LsSf https://astral.sh/uv/install.sh | sh
# 2. Start Neo4j (using Docker)
docker run -d --name neo4j -p 7687:7687 -e NEO4J_AUTH=neo4j/password neo4j:latest
# 3. Create config file
mkdir -p ~/.mcp-kg-skills/config
cat > ~/.mcp-kg-skills/config/database.yaml << 'EOF'
database:
uri: "bolt://localhost:7687"
username: "neo4j"
password: "password"
database: "neo4j"
execution:
cache_dir: "~/.mcp-kg-skills/cache"
env_dir: "~/.mcp-kg-skills/envs"
default_timeout: 300
max_timeout: 600
security:
secret_patterns:
- "^SECRET_"
- "_SECRET$"
- "^.*_KEY$"
- "^.*_PASSWORD$"
- "^.*_TOKEN$"
logging:
level: "INFO"
EOF
# 4. Run the server
uvx mcp-kg-skills
```
That's it! Now configure your MCP client (see [MCP Client Configuration](#mcp-client-configuration)).
## Architecture
```
┌─────────────────────────────────────────────────────┐
│ LLM (Claude via MCP Client) │
└─────────────────┬───────────────────────────────────┘
│ MCP Protocol (FastMCP 2.10)
┌─────────────────▼───────────────────────────────────┐
│ MCP Server (mcp-kg-skills) │
│ ┌───────────────────────────────────────────────┐ │
│ │ Tools: nodes, relationships, env, │ │
│ │ execute, query │ │
│ └────────────────┬──────────────────────────────┘ │
│ ┌────────────────▼──────────────────────────────┐ │
│ │ Script Executor + Secret Protection │ │
│ │ (uv run + PEP 723) │ │
│ └────────────────┬──────────────────────────────┘ │
│ ┌────────────────▼──────────────────────────────┐ │
│ │ Neo4j Database Interface │ │
│ └────────────────┬──────────────────────────────┘ │
└───────────────────┼───────────────────────────────┘
│
┌───────────────────▼───────────────────────────────┐
│ Neo4j Graph Database │
│ Nodes: SKILL, KNOWLEDGE, SCRIPT, ENV │
│ Relationships: CONTAINS, RELATE_TO │
└───────────────────────────────────────────────────┘
```
## Installation
### Prerequisites
- Python 3.12 or higher
- [uv](https://docs.astral.sh/uv/) - Fast Python package installer
- Neo4j 4.4+, 5.x, or 2025.x
- MCP-compatible client (e.g., Claude Desktop)
### Install uv
```bash
# macOS/Linux
curl -LsSf https://astral.sh/uv/install.sh | sh
# Windows
powershell -c "irm https://astral.sh/uv/install.ps1 | iex"
```
### Install MCP Knowledge Graph Skills
#### Option 1: From PyPI (Recommended)
```bash
# Install directly from PyPI
pip install mcp-kg-skills
# Or using uv
uv pip install mcp-kg-skills
# Or run with uvx (no installation needed)
uvx mcp-kg-skills
```
#### Option 2: From Source (Development)
```bash
# Clone the repository
git clone https://github.com/fmktech/mcp-kg-skills.git
cd mcp-kg-skills
# Install with uv
uv pip install -e .
```
### Install Neo4j
#### Option 1: Docker (Recommended)
```bash
docker run -d \
--name neo4j \
-p 7474:7474 -p 7687:7687 \
-e NEO4J_AUTH=neo4j/your-password \
neo4j:latest
```
#### Option 2: Neo4j Desktop
Download from [neo4j.com/download](https://neo4j.com/download/)
#### Option 3: Neo4j Aura (Cloud)
Sign up at [console.neo4j.io](https://console.neo4j.io)
## Configuration
### 1. Create Configuration File
Create the config directory and file:
```bash
# Create config directory
mkdir -p ~/.mcp-kg-skills/config
# Create configuration file
cat > ~/.mcp-kg-skills/config/database.yaml << 'EOF'
database:
uri: "bolt://localhost:7687"
username: "neo4j"
password: "${NEO4J_PASSWORD}" # Or set directly: "your-password"
database: "neo4j"
execution:
cache_dir: "~/.mcp-kg-skills/cache"
env_dir: "~/.mcp-kg-skills/envs"
default_timeout: 300
max_timeout: 600
security:
secret_patterns:
- "^SECRET_"
- "_SECRET$"
- "^.*_KEY$"
- "^.*_PASSWORD$"
- "^.*_TOKEN$"
logging:
level: "INFO"
EOF
```
**If you cloned the repository**, you can copy the example:
```bash
cp .mcp-kg-skills/config/database.yaml.example \
~/.mcp-kg-skills/config/database.yaml
```
### 2. Configure Neo4j Connection
Edit `~/.mcp-kg-skills/config/database.yaml` and update:
- `uri`: Your Neo4j connection URI (e.g., `bolt://localhost:7687` or Neo4j Aura URI)
- `username`: Your Neo4j username (default: `neo4j`)
- `password`: Your Neo4j password (or use environment variable `${NEO4J_PASSWORD}`)
- `database`: Database name (default: `neo4j`)
### 3. Set Environment Variables (Optional)
If using environment variables for passwords:
```bash
# Set Neo4j password
export NEO4J_PASSWORD="your-password"
# Add to your shell profile (~/.bashrc, ~/.zshrc, etc.)
echo 'export NEO4J_PASSWORD="your-password"' >> ~/.zshrc
```
## MCP Client Configuration
### Claude Desktop
Add to your Claude Desktop config file:
- **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
- **Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
- **Linux**: `~/.config/Claude/claude_desktop_config.json`
#### Option 1: Using uvx (Recommended - No Installation Required)
```json
{
"mcpServers": {
"mcp-kg-skills": {
"command": "uvx",
"args": ["mcp-kg-skills"],
"env": {
"NEO4J_PASSWORD": "your-password"
}
}
}
}
```
#### Option 2: Using pip-installed package
```json
{
"mcpServers": {
"mcp-kg-skills": {
"command": "mcp-kg-skills",
"env": {
"NEO4J_PASSWORD": "your-password"
}
}
}
}
```
#### Option 3: Using uv with local development installation
```json
{
"mcpServers": {
"mcp-kg-skills": {
"command": "uv",
"args": [
"--directory",
"/path/to/mcp-kg-skills",
"run",
"mcp-kg-skills"
],
"env": {
"NEO4J_PASSWORD": "your-password"
}
}
}
}
```
**Note**: Replace `/path/to/mcp-kg-skills` with the actual path to your cloned repository.
## Usage
### Node Types
#### SKILL - High-level organizational unit
```python
{
"name": "data-pipeline",
"description": "ETL pipeline for data processing",
"body": "# Data Pipeline\n\nMarkdown content..."
}
```
#### KNOWLEDGE - Documentation and context
```python
{
"name": "api-documentation",
"description": "REST API documentation",
"body": "# API Docs\n\nMarkdown content..."
}
```
#### SCRIPT - Python functions with PEP 723 dependencies
```python
{
"name": "fetch_data",
"description": "Fetch data from API",
"function_signature": "fetch_data(url: str) -> dict",
"body": """
# /// script
# requires-python = ">=3.12"
# dependencies = ["requests>=2.31.0"]
# ///
import requests
def fetch_data(url: str) -> dict:
response = requests.get(url)
response.raise_for_status()
return response.json()
"""
}
```
#### ENV - Environment variable collections
```python
{
"name": "production",
"description": "Production environment variables",
"variables": {
"DATABASE_HOST": "prod.db.example.com",
"DATABASE_PORT": "5432",
"DATABASE_PASSWORD": "secret123" # Auto-detected as secret
}
}
```
### MCP Tools
#### 1. nodes - Manage nodes
**Create a SKILL:**
```python
nodes(
operation="create",
node_type="SKILL",
data={
"name": "data-pipeline",
"description": "ETL data processing pipeline",
"body": "# Data Pipeline\n\nThis skill manages ETL processes..."
}
)
```
**Create a SCRIPT:**
```python
nodes(
operation="create",
node_type="SCRIPT",
data={
"name": "fetch_data",
"description": "Fetch JSON data from URL",
"function_signature": "fetch_data(url: str) -> dict",
"body": """
# /// script
# requires-python = ">=3.12"
# dependencies = ["requests>=2.31.0"]
# ///
import requests
def fetch_data(url: str) -> dict:
response = requests.get(url)
return response.json()
"""
}
)
```
**List nodes:**
```python
nodes(
operation="list",
node_type="SCRIPT",
filters={"name": "fetch", "limit": 10}
)
```
**Read a node:**
```python
nodes(
operation="read",
node_type="SCRIPT",
node_id="script-123"
)
```
**Update a node:**
```python
nodes(
operation="update",
node_type="SCRIPT",
node_id="script-123",
data={"description": "Updated description"}
)
```
**Delete a node:**
```python
nodes(
operation="delete",
node_type="SCRIPT",
node_id="script-123"
)
```
#### 2. relationships - Manage relationships
**Create CONTAINS relationship:**
```python
relationships(
operation="create",
relationship_type="CONTAINS",
source_id="skill-123",
target_id="script-456"
)
```
**Create RELATE_TO relationship:**
```python
relationships(
operation="create",
relationship_type="RELATE_TO",
source_id="skill-123",
target_id="skill-789",
properties={"reason": "related functionality"}
)
```
**List relationships:**
```python
relationships(
operation="list",
source_id="skill-123"
)
```
**Delete relationship:**
```python
relationships(
operation="delete",
rel_id="rel-123"
)
```
#### 3. env - Manage environment variables
**Create environment:**
```python
env(
operation="create",
name="production",
description="Production environment",
variables={
"DATABASE_HOST": "prod.db.example.com",
"DATABASE_PASSWORD": "secret123", # Auto-detected as secret
"API_KEY": "abc123xyz" # Auto-detected as secret
}
)
```
**Read environment (secrets masked):**
```python
env(
operation="read",
env_id="env-123"
)
# Returns: {"DATABASE_HOST": "prod.db.example.com", "DATABASE_PASSWORD": "<SECRET>", ...}
```
**Update environment:**
```python
env(
operation="update",
env_id="env-123",
variables={"NEW_VAR": "value"}
)
```
**List variable keys only:**
```python
env(
operation="list_keys",
env_id="env-123"
)
```
#### 4. execute - Execute Python code
**Execute with imported scripts:**
```python
execute(
code="""
# Imported functions are available by name
data = fetch_data("https://api.example.com/users")
processed = process_users(data)
print(f"Processed {len(processed)} users")
""",
imports=["fetch_data", "process_users"],
timeout=60
)
```
**Execute standalone code:**
```python
execute(
code="print('Hello, World!')",
timeout=10
)
```
#### 5. query - Query the graph
**Find scripts in a skill:**
```cypher
query(
cypher="""
MATCH (s:SKILL {name: $skill_name})-[:CONTAINS]->(script:SCRIPT)
RETURN script.name, script.function_signature
""",
parameters={"skill_name": "data-pipeline"}
)
```
**Find skills using an environment:**
```cypher
query(
cypher="""
MATCH (script:SCRIPT)-[:CONTAINS]->(env:ENV {name: $env_name})
MATCH (skill:SKILL)-[:CONTAINS]->(script)
RETURN DISTINCT skill.name, skill.description
""",
parameters={"env_name": "production"}
)
```
**Explore related skills:**
```cypher
query(
cypher="""
MATCH (s1:SKILL)-[:RELATE_TO]-(s2:SKILL)
WHERE s1.name = $name
RETURN s2.name, s2.description
""",
parameters={"name": "etl-pipeline"}
)
```
## Example Workflow
### 1. Create a Skill
```python
# Create skill
nodes(
operation="create",
node_type="SKILL",
data={
"name": "web-scraper",
"description": "Web scraping utilities",
"body": "# Web Scraper\n\nUtilities for web scraping..."
}
)
# Returns: {"success": true, "node": {"id": "skill-abc123", ...}}
```
### 2. Create Scripts
```python
# Fetch HTML
nodes(
operation="create",
node_type="SCRIPT",
data={
"name": "fetch_html",
"description": "Fetch HTML from URL",
"function_signature": "fetch_html(url: str) -> str",
"body": """
# /// script
# requires-python = ">=3.12"
# dependencies = ["requests>=2.31.0"]
# ///
import requests
def fetch_html(url: str) -> str:
return requests.get(url).text
"""
}
)
# Returns: {"success": true, "node": {"id": "script-def456", ...}}
# Parse HTML
nodes(
operation="create",
node_type="SCRIPT",
data={
"name": "parse_html",
"description": "Extract data from HTML",
"function_signature": "parse_html(html: str) -> dict",
"body": """
# /// script
# requires-python = ">=3.12"
# dependencies = ["beautifulsoup4>=4.12.0"]
# ///
from bs4 import BeautifulSoup
def parse_html(html: str) -> dict:
soup = BeautifulSoup(html, 'html.parser')
return {
'title': soup.title.string if soup.title else None,
'links': [a['href'] for a in soup.find_all('a', href=True)]
}
"""
}
)
# Returns: {"success": true, "node": {"id": "script-ghi789", ...}}
```
### 3. Create Environment
```python
env(
operation="create",
name="scraper-config",
description="Web scraper configuration",
variables={
"USER_AGENT": "MyBot/1.0",
"RATE_LIMIT": "10",
"API_KEY": "secret-key-123" # Auto-detected as secret
}
)
# Returns: {"success": true, "node": {"id": "env-jkl012", ...}}
```
### 4. Link Everything Together
```python
# Skill CONTAINS scripts
relationships(
operation="create",
relationship_type="CONTAINS",
source_id="skill-abc123",
target_id="script-def456"
)
relationships(
operation="create",
relationship_type="CONTAINS",
source_id="skill-abc123",
target_id="script-ghi789"
)
# Scripts CONTAIN environment
relationships(
operation="create",
relationship_type="CONTAINS",
source_id="script-def456",
target_id="env-jkl012"
)
```
### 5. Execute Combined Scripts
```python
execute(
code="""
# Both functions are available
html = fetch_html("https://example.com")
data = parse_html(html)
print(f"Page title: {data['title']}")
print(f"Found {len(data['links'])} links")
""",
imports=["fetch_html", "parse_html"],
timeout=30
)
# Dependencies (requests, beautifulsoup4) are automatically installed
# Environment variables from scraper-config are available
# Secrets are sanitized from output
```
## Security Features
### Automatic Secret Detection
Environment variables matching these patterns are automatically detected as secrets:
- `SECRET_*`
- `*_SECRET`
- `*_KEY`
- `*_PASSWORD`
- `*_TOKEN`
- `*_API_KEY`
- `*_PRIVATE_KEY`
### Secret Protection
1. **Storage**: Secrets are stored in `~/.mcp-kg-skills/envs/*.env` files (outside project directory)
2. **API Responses**: Secret values are replaced with `<SECRET>`
3. **Execution Output**: Secret values are replaced with `<REDACTED>`
4. **File Permissions**: `.env` files are created with `0600` permissions
## Testing
### Quick Start
```bash
# Setup development environment
./dev.sh setup
# Start test services
./dev.sh start
# Run all tests
./dev.sh test
# Run with coverage
./dev.sh test-cov
```
### Test Structure
```
tests/
├── conftest.py # Shared fixtures
├── unit/ # Unit tests (no external dependencies)
│ ├── test_security.py
│ ├── test_dependency_parser.py
│ └── test_models.py
└── integration/ # Integration tests (SQLite by default, Neo4j optional)
├── test_database.py
└── test_end_to_end.py
```
### Database Backends for Testing
Integration tests support two database backends:
- **SQLite (default)**: Fast in-memory testing, no setup required
- **Neo4j (optional)**: Full graph database testing with Cypher queries
```bash
# Run integration tests with SQLite (default - fast, no setup)
pytest tests/integration/
# Run integration tests with Neo4j
export TEST_DB=neo4j
export NEO4J_URI="bolt://localhost:7688"
export NEO4J_PASSWORD="testpassword"
pytest tests/integration/
```
### Running Tests
```bash
# All tests (unit + integration with SQLite)
pytest
# Unit tests only
pytest tests/unit/
# Integration tests with SQLite (default)
pytest tests/integration/
# Integration tests with Neo4j
TEST_DB=neo4j NEO4J_URI=bolt://localhost:7688 NEO4J_PASSWORD=testpassword pytest tests/integration/
# Specific test file
pytest tests/unit/test_security.py -v
# Specific test
pytest tests/unit/test_security.py::TestSecretDetector::test_default_patterns -v
# With coverage
pytest --cov=mcp_kg_skills --cov-report=html
```
### Using the dev.sh Script
```bash
# Run all tests
./dev.sh test
# Run specific tests
./dev.sh test tests/unit/
# Run with coverage report
./dev.sh test-cov
# Format code before committing
./dev.sh format
# Run linter
./dev.sh lint
# Type check
./dev.sh typecheck
```
### Using Make
```bash
# Run tests
make test
# Unit tests only
make test-unit
# Integration tests only
make test-integration
# With coverage
make test-cov
# Code quality checks
make lint format typecheck
```
### Writing Tests
Use pytest fixtures:
```python
import pytest
@pytest.mark.asyncio
async def test_create_node(clean_db, sample_skill_data):
"""Test creating a node."""
node = await clean_db.create_node("SKILL", sample_skill_data)
assert node["id"] is not None
```
See [CONTRIBUTING.md](CONTRIBUTING.md) for detailed testing guidelines.
## Development
### Project Structure
```
mcp-kg-skills/
├── src/mcp_kg_skills/
│ ├── __init__.py
│ ├── server.py # FastMCP server
│ ├── models.py # Pydantic models
│ ├── config.py # Configuration
│ ├── exceptions.py # Custom exceptions
│ ├── database/
│ │ ├── abstract.py # Database interface
│ │ └── neo4j.py # Neo4j implementation
│ ├── execution/
│ │ ├── dependency.py # PEP 723 parser
│ │ └── runner.py # Script executor
│ ├── security/
│ │ └── secrets.py # Secret detection
│ ├── tools/
│ │ ├── nodes.py # Node CRUD
│ │ ├── relationships.py
│ │ ├── env.py
│ │ ├── execute.py
│ │ └── query.py
│ └── utils/
│ └── env_file.py # ENV file manager
├── tests/
├── pyproject.toml
└── README.md
```
### Running Tests
```bash
# Install dev dependencies
uv pip install -e ".[dev]"
# Run tests
pytest
# With coverage
pytest --cov=mcp_kg_skills --cov-report=html
```
### Code Quality
```bash
# Format code
ruff format .
# Lint code
ruff check .
# Type checking
mypy src/
```
## Troubleshooting
### Neo4j Connection Issues
```bash
# Check Neo4j is running
docker ps | grep neo4j
# Check Neo4j logs
docker logs neo4j
# Test connection
neo4j-admin connectivity test
```
### uv Not Found
```bash
# Install uv
curl -LsSf https://astral.sh/uv/install.sh | sh
# Verify installation
uv --version
```
### Permission Errors
```bash
# Fix directory permissions
chmod 700 ~/.mcp-kg-skills/envs/
chmod 600 ~/.mcp-kg-skills/envs/*.env
```
## License
MIT
## Contributing
Contributions are welcome! Please:
1. Fork the repository
2. Create a feature branch
3. Make your changes
4. Add tests
5. Submit a pull request
## Support
- Issues: [GitHub Issues](https://github.com/yourusername/mcp-kg-skills/issues)
- Discussions: [GitHub Discussions](https://github.com/yourusername/mcp-kg-skills/discussions)
TDQS
Scored across 5 tools
Each tool has a clearly distinct responsibility: nodes manages node CRUD, relationships manages edges, env handles environment variables, execute runs scripts, and query performs read-only graph queries. No overlap in purposes.
All tool names are single lowercase words—nodes, relationships, env, execute, query. While some are nouns and some are verbs, the naming convention is perfectly uniform and predictable.
Five tools is an ideal count for this domain. Each tool covers a essential aspect of the knowledge graph skill management workflow, and none feel redundant or missing.
The tool set provides full CRUD for nodes, create/delete/list for relationships, env management, script execution, and graph querying. This is a complete lifecycle covering all obvious operations for the server's purpose.