# Package as Skill

An R package that documents its functions properly is already what MCP is trying to wire together: tools (functions), descriptions (`.Rd` files), and an invocation mechanism (`library()`). corteza walks the `.Rd` tree at session start and turns the exports into JSON-Schema tool definitions — same shape the Anthropic / OpenAI / Moonshot APIs expect. No server, no schema by hand, no protocol.

This vignette walks the setup with fortunes, quotes from the R-help archives. It returns a structured S3 object, and the demo is more fun than configuring a JSON parser.

## Setup

Two steps. Total wiring: one config line.

### 1. Install the package

    install.packages("fortunes")

### 2. Register it as a skill

Add a `skill_packages` entry to corteza’s config. Either project-local at `<cwd>/.corteza/config.json` or global at `tools::R_user_dir("corteza", "config")/config.json`:

    {
      "skill_packages": ["fortunes"]
    }

That’s it. The string form registers every export. For larger packages, the object form picks specific functions:

    {
      "skill_packages": [
        {"package": "fortunes", "functions": ["fortune"]}
      ]
    }

## Use

    corteza::chat()

The startup banner shows the tool count climbing — `corteza::chat()` reports `30 tools` instead of `29`. Then ask the agent to use it:

    > Find a fortune by Brian Ripley about types or coercion.

You’ll see the progress hint fire as the agent invokes the tool:

      [fortunes::fortune] author=Brian Ripley (8 lines)

The `fortune` object comes back with `quote`, `author`, `context`, `source`, and `date` fields, and the agent paraphrases or quotes from there. Try variations:

- “Pick a random fortune and tell me which R personality you’d most love to argue with about it.”
- “Use `fortune(showMatches = TRUE)` to find anything about NULL, then summarize the consensus.”
- “Show me three fortunes about S4 classes and rank them by snark.”

## What makes a package a good fit

The R community has 20,000 CRAN packages but not all of them work cleanly as agent skills. The shape that fits:

- Returns structured data (data.frames, lists, S3 objects). The LLM consumes the result programmatically.
- No printing to stderr on the happy path. Anything that `cat()`s status updates or paints with `crayon::` pollutes the tool result.
- No `oops()` or `stop()` for non-error cases. If the function calls `stop()` because you’re not in the right directory, it’s designed for a human at the console, not a tool harness.
- `.Rd` parameters that match `formals()`. CRAN already enforces this with `R CMD check`, so any current CRAN package qualifies.

Counter-example: gitr wraps `system2("git", ...)` cleanly enough but `cat()`s colored output and calls `oops()` when not in a git repo. Wrapping it as a skill needed `utils::capture.output()` and grew the code instead of shrinking it. Built for humans, not for plumbing.

The test: would you call this function from another function and trust the return value? If yes, it’s a candidate.

## Why no MCP server

A live MCP server ships every tool’s JSON schema into the system prompt at connect time. Twenty tools at ~400 tokens each is 8,000 tokens of startup overhead before the agent has done anything. The CLI / package-as-skill path pays roughly zero startup tax (corteza already has `bash`, `run_r`, `read_file` baked in), and lazy lookup via `saber::pkg_help()` costs ~200 tokens for a tool the agent actually needs.

Same tool surface, different cost curve. corteza’s MCP server (`corteza::serve()`) still exists for clients that need it (Claude Code, Codex, etc), but it’s no longer the only path — the same skill registry feeds both.

## Where to go next

- The derivation pipeline is summarized in the package vignette and package source; the earlier external blog post is no longer linked because its URL no longer resolves reliably.
- Discover what’s in a package the same way the agent does: `Rscript --vanilla -e 'saber::pkg_exports("fortunes")'` and `Rscript --vanilla -e 'saber::pkg_help("fortune", "fortunes")'`.
- For your own functions, drop an `.R` file in `<project>/.corteza/skills/` calling `register_skill_from_fn("name", my_fn)`. Loaded on every session start, no installation required.