Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 79 additions & 0 deletions .claude/skills/coact-publish/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
---
name: coact-publish
description: >-
Publish a Python capability as a deployable AI-chatbot integration with coact —
package tools (module:function refs, live functions, or a skill's coact: mcp
block) into a Claude Desktop one-click .mcpb extension (a local stdio MCP
server). Use when the user wants to create, build, package, or deploy a Claude
connector / plugin / MCP server / .mcpb / Desktop Extension / "integration"
from existing Python code or a skill — e.g. "make an mcpb", "package these
functions for Claude", "turn this into a Claude extension/connector", "publish
a local MCP server", "wrap my tools as a Claude Desktop extension". For REMOTE
claude.ai connectors (HTTPS + OAuth) this is the wrong target — that surface is
not built yet (see Limitations).
metadata:
version: 0.1.0
---

# coact publish — Python capability → Claude integration

`coact publish` ships a capability to a chatbot host. Today it has one target,
`claude-local-mcpb`: a **Claude Desktop `.mcpb` Desktop Extension** that runs a
**local stdio MCP server**. The MCP server itself is built by `py2mcp`; coact
writes the packaging.

## When to use

- The user has **Python functions** (or a skill carrying a `coact: mcp:` block)
and wants them usable inside Claude as tools.
- They ask to "make / build / package / deploy" a Claude **connector**,
**plugin**, **extension**, **`.mcpb`**, or **MCP server** from local code.

## How

Always preview first (writes nothing):

```bash
coact publish my.package.module:my_func another.module:other_func --dry-run
```

Then build the bundle:

```bash
coact publish my.package.module:my_func --name my-tools --dest ~/Downloads
# → ~/Downloads/my-tools.mcpb
```

Sources accepted (mix freely): `module:function` refs, a skill directory /
`SKILL.md` (its `coact: mcp:` block supplies the refs), or — from Python — live
callables and a prebuilt `IntegrationSpec`.

Python API:

```python
from coact import publish, integration_spec_from

publish(["mypkg.tools:summarize", "mypkg.tools:translate"],
name="text-tools", dest="dist", author="Me")
```

Install the result: Claude Desktop → Settings → Extensions → Install Extension…
(or double-click the `.mcpb`). The extension runs **on the user's machine** and
needs a Python with `py2mcp` + `fastmcp` importable.

## Key distinctions (don't conflate)

- **Local `.mcpb` (this target):** stdio, no OAuth, runs on the user's machine.
- **Remote claude.ai connector (NOT this target):** a remote MCP server reached
from Anthropic's cloud over HTTPS + OAuth — a different surface, not yet built.
- A `.mcpb` is *connectivity* (tools). A **Skill** (`SKILL.md`) is *procedural
knowledge*. They are complementary; this skill packages the former.

## Limitations (current)

- Only `claude-local-mcpb`. Remote connectors, Claude Code plugins, ChatGPT
Apps, and Gemini are planned targets (the registry is open-closed).
- The bundle references tools by `module:function`; the **functions must be
importable** in the Python that Claude Desktop runs (full dependency vendoring
into the bundle is a future refinement).
- Background: `misc/docs/CHATBOT_INTEGRATION_LANDSCAPE.md`.
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,31 @@ coact estimate .claude/agents/a.md .claude/agents/b.md # the cost gate
coact inventory . # skills + agents + MCP tools
coact back .claude/agents/ux-analyst.md # lossy agent → skill stub
coact scaffold .claude/agents/a.md .claude/agents/b.md # a starter fleet shim (you own it)
coact publish mypkg.tools:summarize --name my-tools --dry-run # → a Claude .mcpb (preview)
```

## Publish — ship a capability to a chatbot host

Beyond COMPLETE/REALIZE, the **PUBLISH** axis packages a capability (Python
tools) as a deployable chatbot integration. The first target,
`claude-local-mcpb`, builds a **Claude Desktop `.mcpb` Desktop Extension** — a
one-click *local* (stdio) MCP server, built by [`py2mcp`](https://github.com/i2mint/py2mcp):

```python
from coact import publish

publish(["mypkg.tools:summarize", "mypkg.tools:translate"],
name="text-tools", dest="dist") # → dist/text-tools.mcpb
```

Sources can be `module:function` refs, live callables, or a skill carrying a
`coact: mcp:` block. `dry_run=True` (or `--dry-run`) previews the bundle without
writing it. This is the **local** surface (stdio, no OAuth); remote claude.ai
*connectors* (HTTPS + OAuth), Claude Code plugins, ChatGPT Apps, and Gemini are
planned targets on the same open-closed registry. Background:
[`misc/docs/CHATBOT_INTEGRATION_LANDSCAPE.md`](misc/docs/CHATBOT_INTEGRATION_LANDSCAPE.md).
Install: `pip install coact[mcpb]`.

## The model in one minute

A `SKILL.md` is *procedural knowledge injected into the caller's turn*; a subagent
Expand Down
10 changes: 10 additions & 0 deletions coact/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@
RunnableCrewAIAgent,
realize_crewai,
) # registers 'crewai'
from coact.integration import IntegrationSpec, integration_spec_from
from coact.publish import PublishResult, publish, publish_targets
from coact.publish_mcpb import publish_mcpb # registers 'claude-local-mcpb'
from coact.scaffold import scaffold_fleet
from coact.stores import AgentStore, agents_dir
from coact.synthesis import synthesize_persona, synthesize_return_contract
Expand Down Expand Up @@ -120,6 +123,13 @@ def _resolve_version() -> str:
"RunnableLLMGraphAgent",
"RunnableCrewAIAgent",
"realization_backends",
# PUBLISH (ship a capability to a chatbot host; Claude-local .mcpb first; D17)
"IntegrationSpec",
"integration_spec_from",
"publish",
"publish_targets",
"PublishResult",
"publish_mcpb",
# Scaffold (the one topology-adjacent emitter — a starter you own; D8)
"scaffold_fleet",
# Synthesis & LLM facade
Expand Down
30 changes: 28 additions & 2 deletions coact/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
Mirrors ``skill``'s dispatch-to-interface pattern (CLI wrappers call the same
core functions and format for the terminal), so the two packages feel like one
toolkit. The verbs are ``plan``, ``complete``, ``emit``, ``realize``, ``diff``,
``estimate``, ``inventory``, ``back``, and ``scaffold``. Usage::
``estimate``, ``inventory``, ``back``, ``scaffold``, and ``publish``. Usage::

python -m coact plan .claude/skills/ux-analyst
python -m coact complete .claude/skills/ux-analyst --dest .claude/agents
Expand All @@ -16,6 +16,7 @@
python -m coact inventory .
python -m coact back .claude/agents/ux-analyst.md
python -m coact scaffold .claude/agents/a.md .claude/agents/b.md
python -m coact publish my.module:my_func --dry-run
"""

from __future__ import annotations
Expand All @@ -26,6 +27,7 @@
from coact import emit_agent as _emit
from coact import plan_completion as _plan
from coact import realize as _realize
from coact import publish as _publish
from coact import scaffold_fleet as _scaffold_fleet
from coact.analysis import back as _back
from coact.analysis import diff as _diff
Expand Down Expand Up @@ -132,10 +134,34 @@ def scaffold(
return str(result)


@argh.arg(
"source", nargs="+", help="Tool refs ('module:function'), a skill dir/SKILL.md, or both"
)
def publish(
source: list,
*,
target: str = "claude-local-mcpb",
dest: str | None = None,
name: str | None = None,
author: str | None = None,
dry_run: bool = False,
) -> str:
"""Publish a capability to a chatbot host (default: a local Claude Desktop .mcpb bundle).

``--dry-run`` previews the bundle members (manifest + server) without writing
the ``.mcpb``.
"""
src = source if len(source) > 1 else source[0]
res = _publish(
src, target=target, dest=dest, name=name, author=author, dry_run=dry_run
)
return res.render()


def main() -> None:
"""Dispatch the coact CLI."""
argh.dispatch_commands(
[plan, complete, emit, realize, diff, estimate, inventory, back, scaffold]
[plan, complete, emit, realize, diff, estimate, inventory, back, scaffold, publish]
)


Expand Down
Loading
Loading