Skip to content
Agentmart
Submit a tool

lcp_tool_creation

Step-by-step guide to writing LCP tools: schema-driven docstrings, return contracts, requesting privileges via the confirmation handler, and asking the user…

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-creation

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

Step-by-step guide to writing LCP tools: schema-driven docstrings, return contracts, requesting privileges via the confirmation handler, and asking the user for keys, text, or choices with the user-interaction protocol. Includes a complete worked use case.

Use cases: writing

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-creation

What's inside

Skill name
lcp_tool_creation
Description
Step-by-step guide to writing LCP tools: schema-driven docstrings, return contracts, requesting privileges via the confirmation handler, and asking the user for keys, text, or choices with the user-interaction protocol. Includes a complete worked use case.
License
Apache-2.0
Instructions
268 lines in SKILL.md · view on GitHub

Agent Skills spec issues

  • name: missing

Files (1)

  • SKILL.md

SKILL.md (first lines)

# Building LCP Tools — A Practical Creation Guide

This skill teaches you **how to write LCP tools**, not how the engine works. For the binding internals (discovery, AST schema generation, mounting APIs), see the companion skill `lcp_tool_development`.

An LCP tool is a plain Python function named `tool_*` in a `.py` file placed in a tools folder. That's it. No classes, no registration calls, no YAML — the function name, type annotations, defaults, and docstring *are* the tool.

---

## 1. Your First Tool in 60 Seconds

Create `my_tools/greeting.py`:

```python
from typing import Dict

def tool_greet(name: str, excitement_level: int = 1) -> Dict:
    """
    Greets a person with an adjustable level of enthusiasm.

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

---

## 2. The Docstring IS the Schema

The engine reads your source with `ast` — there is no config file. Three rules:

1. **First docstring paragraph** = the description the LLM reads. Make it an instruction: what it does, when to use it.
2. **`Args:` block** = parameter documentation. Use `param (type): description` lines.
3. **Annotations and defaults** = the JSON schema:

   | Write this | LLM sees |
   | :--- | :--- |
   | `name: str` | required string |
   | `count: int = 5` | optional integer, default 5 |
   | `items: list = []` | optional array |
   | `flags: dict = {}` | optional object |

Pitfalls:
- LLMs often pass strings for numbers/booleans — coerce defensively: `count = int(count) if isinstance(count, str) else count`.
- Helpers must NOT start with `tool_` or they become tools.
- `lollms_client_instance`, `discussion_instance`, `tool_context` params are hidden from the LLM but injected at runtime (declare them with `=None` defaults).

---

## 3. The Return Contract

Always return a dict. Success and failure must be unambiguous:

```python
{"success": True, "output": "Human-readable summary the LLM will reason over", ...extra payload keys...}
{"success": False, "error": "Actionable message telling the LLM what went wrong and how to fix it"}
```

- The `output` value is what the LLM actually reads — write it for the model, not for logs.
- Extra keys (`image_filename`, `matches_count`, ...) ride along as payload.
- A special `prompt_injection` key appends guidance to the LLM's next turn (e.g. "show the artefact with `<artefact_image .../>`").
- Catch your own exceptions — a crash still returns an error to the LLM, but your message is more useful than a traceback.

---

## 4. Requesting Privileges (Confirmation Handler)

When a tool does something **sensitive, irreversible, or privileged** — network calls, subprocesses, deleting files, spending money — gate it behind the host's confirmation handler. The handler is delivered to your module automatically (via `init_tools_library` config or the `set_confirm_handler` hook):

```python
_CONFIRM_HANDLER = None

def set_confirm_handler(handler):
    global _CONFIRM_HANDLER
    _CONFIRM_HANDLER = handler

def init_tools_library(config=None):
    global _CONFIRM_HANDLER
    if config and isinstance(config, dict):
        _CONFIRM_HANDLER = config.get("confirm_handler", _CONFIRM_HANDLER)

def tool_deploy_website(site_name: str, target_dir: str) -> Dict:
    """
    Deploys the workspace site to a production directory.

    Args:
        site_name (str): Name of the site to deploy.
        target_dir (str): Destination directory for the deployment.
    """
    if _CONFIRM_HANDLER is not None:
        source = f"Copy built assets of '{site_name}' to '{target_dir}'"
        decision, reason = _CONFIRM_HANDLER(source, f"Deploy website '{site_name}'")
        if decision not in ("allow", "always"):
            return {"success": False,
                    "error": f"Deployment denied by operator: {reason or 'no reason given'}"}
    # ... perform the deployment ...
    return {"success": True, "output": f"Site '{site_name}' deployed to {target_dir}."}
```

Contract: `confirm_handler(source, script_label, argv=None) -> (decision, reason)` where `decision` is `"allow"`, `"always"` (auto-approve for the session), or `"reject"`. If no handler is registered (`None`), decide your own fail-safe policy: skip the privileged step and explain, or fail with a clear error. Never silently proceed.

---

## 5. Asking the User for Input (User Interaction Protocol)

Some tools cannot proceed without information only the human has: **an API key, a clarification, a choice between options**. The user-interaction protocol lets a tool pause and ask.

### The handler contract

The host registers one callback. Your tool sends a request dict, gets back a string (or `None` on cancel):

```python
def user_interaction_handler(request: dict) -> Optional[str]:
    # request: {"type": "text" | "secret" | "select",
    #           "title": str,           # short headline, e.g. "OpenWeather API key"
    #           "prompt": str,          # the question to show the user
    #           "options": List[str],   # only for "select"

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_creation skill. Agent Mart. https://agentmart.dev/skills/parisneo-lcp-tool-creation (last updated 2026-10-24)