Decorating Methods

How to safely expose instance, class, and static methods as tools, resources, or prompts in Hyperia Protocol.

TL;DR — bind first, decorate second. Hyperia decorators capture callables at import‑time; if those callables still expect self or cls, the LLM will also have to supply them—and it obviously can’t. The fix is to register bound callables after objects/classes exist.


1 Why Methods Are Tricky

Python applies decorators when the function object is created—i.e. during class body execution, before you have an instance. At that moment:

  • def foo(self, x): … is an unbound function with the first positional parameter named self.

  • @classmethod is applied after other decorators in the chain (because decorators stack bottom‑to‑top).

Hence decorating directly gives Hyperia a callable that still requires self or cls.


2 Working Patterns

2.1 Instance Methods

Wrong way — the LLM will see a self arg:

from hyperia import Hyperia
mcp = Hyperia("Broken")

class Calc:
    @mcp.tool()
    def add(self, a: int, b: int):            # <- self leaks
        return a + b

Correct — register after instantiation:

mcp = Hyperia("CalcAPI")
class Calc:
    def add(self, a: int, b: int): return a + b

calc = Calc()
# Bound method; self already captured
mcp.add_tool(calc.add)

You can also shortcut with functools.partial if you need to pre‑bind only some args.

2.2 Class Methods

Decorator order pitfalls:

class Doc:
    @classmethod          # applied last -> ok for Python, bad for Hyperia
    @mcp.tool()           # captures raw function BEFORE cls binding
    def from_json(cls, js): return cls(**js)

Fix: define normally, then register:

class Doc:
    @classmethod
    def from_json(cls, js): return cls(**js)

mcp.add_tool(Doc.from_json)   # Doc.from_json is a descriptor bound to class

2.3 Static Methods

No binding needed → direct decoration is safe:

class Math:
    @staticmethod
    @mcp.tool()
    def mul(a: int, b: int): return a * b

Under the hood staticmethod converts the function into a plain callable before Hyperia sees it.


3 Bulk Registration Patterns

3.1 Self‑Registering Components

Encapsulate registration inside __init__:

class ReportModule:
    def __init__(self, mcp: Hyperia):
        mcp.add_tool(self.generate)
        mcp.add_resource_fn(self.status, uri="resource://report/status")

    def generate(self, title: str): ...
    def status(self): return "idle"

ReportModule(mcp)

Keeps your feature module cohesive; useful when distributing as pip packages.

3.2 Mixin Helpers

Provide a base class with a register(self, mcp) helper:

class HyperiaMixin:
    def register(self, mcp: Hyperia):
        for name in dir(self):
            attr = getattr(self, name)
            if getattr(attr, "_as_hyperia_tool", False):
                mcp.add_tool(attr)

class Tasks(HyperiaMixin):
    def __init__(self):
        self.counter = 0

    @property
    def _as_hyperia_tool(self): return True
    def ping(self): return "pong"

4 Decorator Helper Cheatsheet

Desired Component
Inline Decorator Safe?
Alternate API

Function

n/a

@staticmethod

mcp.add_tool(cls.method)

@classmethod

mcp.add_tool(Class.method)

Instance method

obj = Class(); mcp.add_tool(obj.method)

Resource template

Only with staticmethod

mcp.add_resource_fn(...)

Prompt

same rules as tool

mcp.add_prompt(...)


5 Advanced Tricks

5.1 Decorator Factory to Keep Syntax Sugary

If you love @mcp.tool() syntax but need late binding:

from functools import wraps

def instance_tool():
    def wrapper(fn):
        fn._is_instance_tool = True   # mark it
        return fn
    return wrapper

class Greeter:
    @instance_tool()
    def hello(self, name: str):
        return f"Hi {name}!"

g = Greeter()
for name, attr in Greeter.__dict__.items():
    if getattr(attr, "_is_instance_tool", False):
        mcp.add_tool(getattr(g, name))

5.2 Partial Application for Stateful Helpers

Sometimes you need to bind some state but keep others open:

from functools import partial
config = {...}

def fetch(url, cfg): ...

mcp.add_tool(partial(fetch, cfg=config))

6 Summary Checklist

  1. Never expose a naked self/cls to the LLM.

  2. Register bound callables using .add_tool, .add_resource_fn, or .add_prompt.

  3. Static methods are safe to decorate inline; others are not.

  4. Use helper patterns (self‑registering classes, mixins, partials) to keep code DRY.

Following these patterns keeps your OOP design tidy and ensures Hyperia surfaces clean, intuitive APIs to LLMs and other clients.

Last updated