Skip to content
Agentmart
Submit a tool

lcp_tool_building

Practical how-to for authoring LCP tools: tool function anatomy, docstring-driven schemas, requesting execution privileges via the confirm handler, and…

Skill by ParisNeo

IndexedApache-2.0★ 13
Write a review

Install

Works with Claude Code, Claude apps, Codex, Cursor, Gemini CLI, GitHub Copilot, VS Code, OpenCode, Goose, Windsurf. Read the repository before you give any tool access to your data.

npx agentmarket install skill/parisneo-lcp-tool-building

Asks which of your tools to add it to (Claude Code, Codex, Cursor, VS Code, Windsurf, Gemini CLI, Claude Desktop) and shows the change first. See https://agentmart.dev/cli.

Get the weekly diff

The weekly diff: one email a week with new agent tools, notable updates and grade changes, plus security alerts for tools you follow. Double opt-in, one-click unsubscribe.

We use your email only for this newsletter. Privacy

About

Practical how-to for authoring LCP tools: tool function anatomy, docstring-driven schemas, requesting execution privileges via the confirm handler, and collecting user input (text, secrets, select-from-list) mid-execution from the CLI or GUI.

Use cases: security

No reviews yet. Installed it? Reviews come from verified installers only. How reviews work

Permissions

What this skill can do and what it asks for, from its SKILL.md and code. Checked Oct 25, 2026.

Tools
No tools found
Secrets
None asked for
Install scripts
None found

Network hosts it talks to 1

  • codeload.github.com · found in install command

Files it touches

  • ~/.claude/skills/lcp-tool-building

What's inside

Skill name
lcp_tool_building
Description
Practical how-to for authoring LCP tools: tool function anatomy, docstring-driven schemas, requesting execution privileges via the confirm handler, and collecting user input (text, secrets, select-from-list) mid-execution from the CLI or GUI.
License
Apache-2.0
Instructions
260 lines in SKILL.md · view on GitHub

Agent Skills spec issues

  • name: missing

Files (1)

  • SKILL.md

SKILL.md (first lines)

# Building LCP Tools: Privilege Requests & User Input Collection

A hands-on guide to **writing** LCP tools. For binding architecture, discovery internals, and mounting APIs, see the `lcp_tool_development` skill. This skill covers only what a tool author needs: function anatomy, schemas, asking for privileges, and asking the user for information.

---

## 1. Minimal Tool Anatomy

An LCP tool is a plain Python function named `tool_*` in a `.py` file inside a tools folder (e.g. `.lollms_code/tools/my_tool/my_tool.py`):

```python
from typing import Any, Dict

def tool_greet(name: str, enthusiasm: int = 1) -> Dict[str, Any]:
    """
    Greets a person with adjustable enthusiasm.

    Args:
        name (str): The name of the person to greet.
        enthusiasm (int): Number of exclamation marks (1-5).
    """
    return {"success": True, "output": f"Hello {name}" + "!" * max(1, min(5, enthusiasm))}
```

**Rules that matter to you:**
- The **function name** is the tool name (`tool_greet`). Prefix anything else with `_` to keep it private.
- The **first docstring paragraph** becomes the LLM-facing description.
- `Args:` lines in the form `name (type): desc` document parameters.
- Annotations drive the schema: `int→integer`, `float→number`, `bool→boolean`, `list→array`, `dict→object`, everything else `→string`.
- A parameter **with a default is optional**; without one, it is required.
- Always return `{"success": True, "output": ...}` or `{"success": False, "error": ...}`.
- **Coerce defensively** — LLMs often pass strings for numbers:

```python
if isinstance(enthusiasm, str):
    try:
        enthusiasm = int(enthusiasm)
    except ValueError:
        enthusiasm = 1
```

---

## 2. Requesting Privileges (Complex / Sensitive Actions)

When a tool performs risky or irreversible work, gate it behind the host's **confirmation handler**. You never prompt the user yourself — you ask the host, and the host decides how to ask (terminal prompt, GUI modal, auto-approve).

### How your tool receives the confirm handler

Declare the module hooks; the binding injects the handler at lazy load, either via `init_tools_library(config)` (host_tool_configs) or via `set_confirm_handler`:

```python
from typing import Any, Dict, Optional

_CONFIRM_HANDLER = None

def set_confirm_handler(handler: Optional[Any]) -> None:
    global _CONFIRM_HANDLER
    _CONFIRM_HANDLER = handler

def init_tools_library(config: Optional[dict] = None) -> None:
    global _CONFIRM_HANDLER
    if config and isinstance(config, dict):
        _CONFIRM_HANDLER = config.get("confirm_handler", _CONFIRM_HANDLER)
```

### Calling it

```python
decision, reason = _CONFIRM_HANDLER(
    source,              # what is about to run (code / command / description)
    script_label,        # short human-readable label
    argv=None,           # optional argument list
)
```

| Return | Meaning | Your action |
| :--- | :--- | :--- |
| `("allow", "")` | Approved once | Proceed |
| `("always", "")` | Approved for the session | Proceed (and skip future prompts if you track it) |
| `("reject", reason)` | Refused | **Stop** and return `{"success": False, "error": reason}` — the reason is fed back to the LLM so it can adapt |

### Autonomy awareness

Hosts configure an autonomy level (`strict` / `safe` / `full_access`) for libraries like `execute_python` and `system_shell`. If your tool is autonomy-aware, read it from the host config:

```python
AUTONOMY_LEVEL = "safe"

def init_tools_library(config: Optional[dict] = None) -> None:
    global AUTONOMY_LEVEL
    if config and isinstance(config, dict):
        AUTONOMY_LEVEL = config.get("autonomy_level", "safe")
```

Honor it: auto-run only benign paths in `safe`, and never assume `full_access`.

---

## 3. Requesting User Input (text, secrets, select)

Sometimes a tool needs **information only the human has** — an API key, a filename, a choice between strategies. LCP supports this with the **user interaction handler**.

### How your tool receives it

Declare `user_interaction_handler=None` as a parameter of your tool function. The binding **hides it from the LLM schema** and injects the live host handler at execution time (fallback: capture it via `init_tools_library(config)` / `set_user_interaction_handler` like the confirm handler above).

```python
def tool_prepare_deployment(
    action: str = "deploy",
    target: str = "production",
    user_interaction_handler=None,   # hidden from the LLM, injected by the host
) -> Dict[str, Any]:
    ...
```

### The request contract

Call the handler with a dict; it returns `Optional[str]` (`None` = user cancelled or timed out):

From ParisNeo/lollms_client, Apache-2.0.

More from ParisNeo/lollms_client

Signals

Stars
13
Forks
5
Open issues
0
Last commit (this folder)
Oct 12, 2026 (16 days ago)
Last release
Mar 5, 2025 (1 year ago)

From GitHub (fetched Oct 24, 2026).

Details

License
Apache-2.0
First seen
Oct 24, 2026
Last checked
Oct 24, 2026

More from ParisNeo

Cite this page

lcp_tool_building skill. Agent Mart. https://agentmart.dev/skills/parisneo-lcp-tool-building (last updated 2026-10-24)