Skip to content
PACKS

Registering Tools and Lead Sources

How to register MCP tools with input schemas and risk levels, and implement LeadSource subclasses for the multi-channel lead hunting system.

MCP tools

Register tools in your pack.py via context.mcp_client.register_builtin_handler():

def register(self, context: PackContext) -> None:
    if context.mcp_client is None:
        return

    async def weather_forecast(city: str, days: int = 3) -> str:
        import json
        # Your implementation here
        return json.dumps({"city": city, "forecast": "sunny", "days": days})

    context.mcp_client.register_builtin_handler(
        "weather_forecast",
        weather_forecast,
        description="Get weather forecast for a city",
        input_schema={
            "type": "object",
            "properties": {
                "city": {"type": "string", "description": "City name"},
                "days": {"type": "integer", "description": "Forecast days (1-7)", "default": 3},
            },
            "required": ["city"],
        },
        risk_level="green",
    )

Handler requirements

  • Must be async def
  • Return a JSON-serializable string
  • Parameters must match the input_schema properties
  • Handle errors gracefully — return error info as JSON, do not crash

Risk levels

The Gatekeeper classifies every tool call. Set risk_level to control the default:

Level Behavior Use for
"green" Auto-execute Read-only, no side effects
"yellow" Execute + inform user Local writes, safe mutations
"orange" User must approve Network calls, deletions, external APIs
"red" Blocked by default Destructive, irreversible

If you omit risk_level, unknown tools default to orange (user approval required).

Lead sources

Implement a LeadSource subclass to add a new channel to the lead hunting system.

Required class variables

from cognithor.leads.source import LeadSource
from cognithor.leads.models import Lead

class MySource(LeadSource):
    source_id = "my-platform"
    display_name = "My Platform"
    icon = "forum"           # Material icon name
    color = "#FF6B35"        # Hex color for UI
    capabilities = frozenset({"scan"})

Required method: scan()

    async def scan(
        self,
        *,
        config: dict,
        product: str,
        product_description: str,
        min_score: int,
    ) -> list[Lead]:
        posts = await self._fetch_posts(config)
        leads = []
        for post in posts:
            score = await self._score(post, product, product_description)
            if score >= min_score:
                leads.append(Lead(
                    post_id=f"my-platform-{post['id']}",
                    source_id=self.source_id,
                    title=post["title"],
                    url=post["url"],
                    intent_score=score,
                    body=post.get("body", ""),
                    author=post.get("author", ""),
                ))
        return leads

Optional methods

Add capabilities to enable these:

Capability Method Signature
"draft_reply" draft_reply(lead, *, tone) Returns reply text
"refine_reply" refine_reply(lead, draft) Returns refined text
"auto_post" post_reply(lead, text) Posts to platform

Registration

def register(self, context: PackContext) -> None:
    if context.leads:
        self._source = MySource()
        context.leads.register_source(self._source)

def unregister(self, context: PackContext) -> None:
    if context.leads and self._source:
        context.leads.unregister_source(self._source.source_id)