Source code for acquaint.mcp

"""MCP over stdio: the same tools, for Claude Desktop and other local MCP clients.

Run ``acquaint-mcp`` (install the extra first: ``pip install "acquaint[mcp]"``). The
exposed tools are named by ``acquaint.tools:<name>`` reference, so the core never imports
an MCP library. By default only tools that read locally, append or create are exposed;
renaming, forgetting and syncing are operator actions and stay at the terminal.

The data root is the server's, never the model's: ``data_dir`` is removed from every
tool's schema, so a model cannot write profiles into whatever directory it is working
in. ``remember``'s ``reactivate`` is removed too: making an inactive identity active
again changes a recorded status, which is the operator's call. Point the server
elsewhere with ``ACQUAINT_DATA_DIR`` in the client configuration::

    {"mcpServers": {"acquaint": {"command": "acquaint-mcp"}}}
"""

from __future__ import annotations

import functools
import importlib
import inspect
from collections.abc import Callable, Iterable

from acquaint.tools import SIDE_EFFECTS, TOOLS

__all__ = [
    "DEFAULT_EFFECTS",
    "HIDDEN_PARAMETERS",
    "INSTRUCTIONS",
    "main",
    "mk_server",
    "refs",
]

DEFAULT_EFFECTS = ("read", "append", "create")
HIDDEN_PARAMETERS = ("data_dir", "reactivate")
INSTRUCTIONS = (
    "Local profiles of the people (and projects, orgs, groups) the user works with. "
    "Use `who` for one fact about someone, `brief` before writing to someone, `check` on prose "
    "that names people before it is published, `style_lint` on a draft, and `remember` to "
    "record something learned, always with its source. Tools never guess: pass exact names or "
    "ids. Never record health, religion, politics, ethnicity, sexuality, credentials, or "
    "personality labels."
)


[docs] def refs(*, effects: Iterable[str] = DEFAULT_EFFECTS) -> list[str]: """``acquaint.tools:<name>`` references for the tools whose side effects are in ``effects``. >>> "acquaint.tools:brief" in refs(), "acquaint.tools:forget" in refs(), "acquaint.tools:sync_status" in refs() (True, False, False) """ allowed = set(effects) return [ f"acquaint.tools:{t.__name__}" for t in TOOLS if SIDE_EFFECTS[t.__name__] in allowed ]
def _resolve(ref: str) -> Callable: module, _, name = ref.partition(":") return getattr(importlib.import_module(module), name) def _without(func: Callable, hidden: Iterable[str] = HIDDEN_PARAMETERS) -> Callable: """The same tool with ``hidden`` parameters removed from its signature (and so from its MCP schema).""" hidden = set(hidden) signature = inspect.signature(func) @functools.wraps(func) def tool(*args, **kwargs): return func(*args, **kwargs) tool.__signature__ = signature.replace( parameters=[p for p in signature.parameters.values() if p.name not in hidden] ) return tool
[docs] def mk_server(*, effects: Iterable[str] = DEFAULT_EFFECTS): """A FastMCP server over the selected tools, with :data:`HIDDEN_PARAMETERS` hidden (not started).""" from py2mcp import mk_mcp_server return mk_mcp_server( [_without(_resolve(ref)) for ref in refs(effects=effects)], name="acquaint", instructions=INSTRUCTIONS, )
[docs] def main() -> None: """Serve the default tools over stdio.""" try: server = mk_server() except ImportError as error: raise SystemExit( 'acquaint-mcp needs the mcp extra: pip install "acquaint[mcp]"' ) from error server.run()
if __name__ == "__main__": main()