Skip to content

Contributing

import { Aside } from ‘@astrojs/starlight/components’;

Ways to contribute

  • New OSINT tool: integrate a new service as an async function
  • Bug fix: correct incorrect behavior
  • Documentation improvement: clarify, complete, translate
  • Tests: increase test coverage
  • New entity type: extend the FTM ontology
  • Skill translation: adapt for new languages

Workflow

Ventana de terminal
# 1. Fork the repository on GitHub
# 2. Clone your fork
git clone https://github.com/your-username/osint-ai-one.git
cd osint-ai-one
# 3. Create a branch for your change
git checkout -b feature/my-new-tool
# 4. Install in development mode
python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
pre-commit install
# 5. Make your changes
# ...
# 6. Run tests
pytest tests/ -v
ruff check src/ tests/
mypy src/
# 7. Commit and push
git add .
git commit -m "feat: add tool X to query Y"
git push origin feature/my-new-tool
# 8. Open a Pull Request on GitHub

Adding a new OSINT tool

Function structure

All tools follow the same pattern:

src/tools/my_service.py
import os
import httpx
from src.cache import cached
@cached(ttl_seconds=86400)
async def my_service_lookup(target: str) -> dict:
"""
Query My Service for information about target.
Args:
target: IP, domain or other identifier
Returns:
dict with normalized fields from the service
"""
api_key = os.getenv("MY_SERVICE_API_KEY")
if not api_key:
return {"error": "MY_SERVICE_API_KEY not configured"}
async with httpx.AsyncClient(timeout=30) as client:
response = await client.get(
f"https://api.myservice.com/v1/lookup/{target}",
headers={"Authorization": f"Bearer {api_key}"}
)
response.raise_for_status()
data = response.json()
return {
"source": "myservice",
"target": target,
"result_field": data.get("field"),
# ... normalize relevant fields
}

Register in the agent

In src/agent/__init__.py, add the tool to the agent’s ReAct tools list.

Register in the MCP Server

In src/mcp_server/server.py, add an MCP endpoint for the tool.

Required tests

tests/tools/test_my_service.py
import pytest
import respx
from httpx import Response
from src.tools.my_service import my_service_lookup
@pytest.mark.asyncio
@respx.mock
async def test_my_service_lookup_success():
respx.get("https://api.myservice.com/v1/lookup/8.8.8.8").mock(
return_value=Response(200, json={"field": "value"})
)
result = await my_service_lookup("8.8.8.8")
assert result["result_field"] == "value"
assert result["source"] == "myservice"
@pytest.mark.asyncio
@respx.mock
async def test_my_service_lookup_error():
respx.get("https://api.myservice.com/v1/lookup/8.8.8.8").mock(
return_value=Response(404)
)
result = await my_service_lookup("8.8.8.8")
assert "error" in result

Document in .env.example

Ventana de terminal
# My Service — https://myservice.com/register
MY_SERVICE_API_KEY=

Code conventions

ElementConvention
Tool functionsservice_name_type_lookup
Environment variablesSERVICE_API_KEY in uppercase
ImportsOrdered: stdlib → third-party → local
Formatruff format (line length: 88)
Type hintsMandatory in public functions
DocstringsGoogle style, in English

Commit conventions

feat: new tool for X
fix: fixes error in Y when Z
docs: adds documentation for W
test: tests for tool V
refactor: extracts shared cache logic
chore: updates dependencies

Code of Conduct

Respectful and constructive contributions. Critique the code, not the people.