Skip to content
Open
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
Binary file added assets/etsy-manager/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 10 additions & 0 deletions external_plugins/etsy-manager/.mcp.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"mcpServers": {
"etsy-manager": {
"command": "node",
"args": [
"${CLAUDE_PLUGIN_ROOT}/scripts/mcp-server.mjs"
]
}
}
}
15 changes: 15 additions & 0 deletions external_plugins/etsy-manager/.zcode-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"name": "etsy-manager",
"version": "0.1.1",
"description": "Manage Etsy shop products: create, update, delete listings, upload images, manage inventory, shipping and orders via the Etsy Open API v3",
"description_i18n": {
"en": "Manage Etsy shop products: create, update, delete listings, upload images, manage inventory, shipping and orders via the Etsy Open API v3",
"zh-CN": "管理 Etsy 店铺商品:通过 Etsy Open API v3 创建、更新、删除商品(listing),上传图片,管理库存、物流与订单。"
},
"author": {
"name": "Hugo",
"url": "https://gozutok.info"
},
"skills": "./skills",
"mcpServers": "./.mcp.json"
}
87 changes: 87 additions & 0 deletions external_plugins/etsy-manager/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# etsy-manager

A [ZCode](https://zcode.z.ai) plugin for managing an Etsy shop: listings, images,
inventory, shipping profiles, orders and reviews — 41 tools on top of the
Etsy Open API v3, powered by a vendored copy of
[aserper/etsy-mcp](https://github.com/aserper/etsy-mcp) (MIT).

The plugin is fully **standalone**: the MCP server is bundled with esbuild into a
single dependency-free file (`vendor/etsy-mcp/server-bundle.mjs`, ~1.3 MB), so it
runs on Windows, Linux and macOS without `npm install`. No credentials are stored
inside the plugin — keys are read from your environment or `~/.etsy-mcp/config.json`.

[README (Türkçe)](./README_TR.md) · [README (中文)](./README_CN.md)

## Requirements

- ZCode (Desktop or CLI)
- **Node.js ≥ 20** on `PATH` (`node` must be runnable). On a VPS:
`nvm install 22` or NodeSource setup.

## First-time setup

1. Get your app's **keystring** (`apiKey`) and **shared secret** (`sharedSecret`)
from https://www.etsy.com/developers/apps. When registering an app, set its
callback URL to `http://localhost:3737/callback`.
2. Create `~/.etsy-mcp/config.json`:
```json
{ "apiKey": "your-keystring", "sharedSecret": "your-shared-secret" }
```
(Environment variables `ETSY_API_KEY` / `ETSY_SHARED_SECRET` take precedence.)
3. In the chat say **"connect to Etsy"** — the `authenticate` tool returns an
OAuth consent URL. Approve it once; tokens are saved to
`~/.etsy-mcp/tokens.json` and refreshed automatically.

### Headless machines / VPS

The `authenticate` tool returns the consent URL directly in the chat and never
blocks. If the server has no browser, forward the callback port over SSH from
your local computer and open the URL there:

```bash
ssh -L 3737:localhost:3737 user@your-vps
```

Setting `ETSY_NO_BROWSER=1` disables the browser-launch attempt entirely.
Alternatively, copy an existing `~/.etsy-mcp/tokens.json` from an authenticated
machine to skip OAuth (tokens are machine-independent and auto-refresh; treat
the file as a secret).

## Vendored-server patches

- OAuth callback port is fixed (default 3737, override with `ETSY_CALLBACK_PORT`)
so the redirect URI can be registered in the Etsy app dashboard.
- Credentials are read from `~/.etsy-mcp/config.json` (env vars win).
- Headless/VPS support: the consent URL is printed **and** returned by the
`authenticate` tool before any browser launch; the tool is non-blocking;
browser-launch failure never breaks the flow; `ETSY_NO_BROWSER=1` skips it.
- The OAuth callback server binds to loopback only (`127.0.0.1`) — safe on a
VPS and reachable through an SSH tunnel; a busy port fails fast with a clear
error instead of hanging.

## Side effects

- Runs a local MCP server (`node scripts/mcp-server.mjs`) that makes HTTPS
requests to `api.etsy.com` / `www.etsy.com`.
- During OAuth, starts a temporary HTTP server on `127.0.0.1:3737`
(`ETSY_CALLBACK_PORT`) for up to 2 minutes, and tries to open the consent
URL in a browser (skippable via `ETSY_NO_BROWSER=1`).
- Reads/writes only inside `~/.etsy-mcp/` (`config.json` you create,
`tokens.json` it manages). Nothing else is written or read.

## Structure

```
.zcode-plugin/plugin.json plugin manifest
.mcp.json MCP server definition (node scripts/mcp-server.mjs)
scripts/mcp-server.mjs credential-reading launcher
skills/etsy-manager/SKILL.md usage guide for the model
vendor/etsy-mcp/server-bundle.mjs self-contained MCP server (esbuild bundle)
vendor/etsy-mcp/src/ patched upstream TypeScript sources (provenance)
```

## License

MIT — Copyright (c) 2026 [Hugo](https://gozutok.info). Vendored
[aserper/etsy-mcp](https://github.com/aserper/etsy-mcp) is MIT
([its license](./vendor/etsy-mcp/LICENSE)).
79 changes: 79 additions & 0 deletions external_plugins/etsy-manager/README_CN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# etsy-manager(中文)

用于管理 Etsy 店铺的 [ZCode](https://zcode.z.ai) 插件:商品(listing)、图片、
库存、物流方案、订单与评价 —— 基于 Etsy Open API v3 的 41 个工具,内置
[aserper/etsy-mcp](https://github.com/aserper/etsy-mcp)(MIT)的服务端。

插件完全**独立运行**:MCP 服务端经 esbuild 打包为单个无依赖文件
(`vendor/etsy-mcp/server-bundle.mjs`,约 1.3 MB),在 Windows / Linux / macOS 上
无需 `npm install` 即可使用。插件本身不保存任何凭据 —— 密钥从环境变量或
`~/.etsy-mcp/config.json` 读取。

[English README](./README.md) · [Türkçe README](./README_TR.md)

## 环境要求

- ZCode(桌面版或 CLI)
- **Node.js ≥ 20**(`node` 命令可用)。VPS 上可用 `nvm install 22` 安装。

## 首次配置

1. 在 https://www.etsy.com/developers/apps 获取应用的 **keystring**(`apiKey`)与
**shared secret**(`sharedSecret`)。新建应用时回调 URL 填
`http://localhost:3737/callback`。
2. 创建 `~/.etsy-mcp/config.json`:
```json
{ "apiKey": "你的keystring", "sharedSecret": "你的shared-secret" }
```
(环境变量 `ETSY_API_KEY` / `ETSY_SHARED_SECRET` 优先。)
3. 在对话中说 **"连接 Etsy"** —— `authenticate` 工具会返回 OAuth 授权链接,
授权一次后令牌保存到 `~/.etsy-mcp/tokens.json` 并自动刷新。

### 无界面服务器 / VPS

`authenticate` 工具直接在对话中返回授权链接且不阻塞。若服务器没有浏览器,在本地
电脑通过 SSH 转发回调端口后在本机浏览器打开链接:

```bash
ssh -L 3737:localhost:3737 user@your-vps
```

设置 `ETSY_NO_BROWSER=1` 可完全跳过启动浏览器的尝试。也可以直接从已授权的机器
复制 `~/.etsy-mcp/tokens.json` 跳过 OAuth(令牌与机器无关且自动刷新;请把该文件
当作机密妥善保管)。

## 对内置服务端的修改

- OAuth 回调端口固定(默认 3737,可用 `ETSY_CALLBACK_PORT` 覆盖),便于在 Etsy
应用后台登记回调地址。
- 凭据从 `~/.etsy-mcp/config.json` 读取(环境变量优先)。
- 无界面/VPS 支持:授权链接在尝试打开浏览器**之前**即打印并作为 `authenticate`
工具结果返回;工具不阻塞;打开浏览器失败不影响流程;`ETSY_NO_BROWSER=1` 可跳过。
- OAuth 回调服务器仅绑定回环地址(`127.0.0.1`)—— VPS 上安全,且可通过 SSH 隧道
访问;端口被占用时立即报错而不是挂起。

## 副作用说明

- 运行本地 MCP 服务器(`node scripts/mcp-server.mjs`),向 `api.etsy.com` /
`www.etsy.com` 发起 HTTPS 请求。
- OAuth 期间在 `127.0.0.1:3737`(`ETSY_CALLBACK_PORT`)上临时开启最多 2 分钟的
HTTP 服务器,并尝试在浏览器打开授权链接(可用 `ETSY_NO_BROWSER=1` 跳过)。
- 仅读写 `~/.etsy-mcp/` 目录(你创建的 `config.json`、插件管理的 `tokens.json`),
不读写其他任何文件。

## 目录结构

```
.zcode-plugin/plugin.json 插件清单
.mcp.json MCP 服务器定义(node scripts/mcp-server.mjs)
scripts/mcp-server.mjs 读取凭据的启动器
skills/etsy-manager/SKILL.md 模型使用指南
vendor/etsy-mcp/server-bundle.mjs 无依赖 MCP 服务端(esbuild 打包)
vendor/etsy-mcp/src/ 打过补丁的上游 TypeScript 源码
```

## 许可证

MIT — Copyright (c) 2026 [Hugo](https://gozutok.info)。内置的
[aserper/etsy-mcp](https://github.com/aserper/etsy-mcp) 采用 MIT 许可证
([其许可证](./vendor/etsy-mcp/LICENSE))。
92 changes: 92 additions & 0 deletions external_plugins/etsy-manager/README_TR.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# etsy-manager (Türkçe)

Etsy mağaza ve ürün (listing) yönetimi için ZCode plugin'i. İçinde vendor'lanmış
[aserper/etsy-mcp](https://github.com/aserper/etsy-mcp) (MIT) MCP server'ı barındırır:
Etsy Open API v3 üzerinden 41 araç — listing create/update/delete, görsel/video yükleme,
envanter/stok, kargo profilleri, siparişler, yorumlar.

Plugin **tek başına (standalone)** çalışır: MCP sunucusu esbuild ile tek bir
bağımlılıksız dosyaya derlenir (`vendor/etsy-mcp/server-bundle.mjs`, ~1.3 MB) ve
Windows / Linux / macOS üzerinde `npm install` gerektirmeden çalışır. Plugin
içinde kimlik bilgisi tutulmaz — anahtarlar ortam değişkenlerinden veya
`~/.etsy-mcp/config.json`'dan okunur.

[English README](./README.md) · [中文说明](./README_CN.md)

## Gereksinimler

- ZCode (Desktop veya CLI)
- **Node.js ≥ 20** PATH'te (`node` komutu çalışmalı). VPS'te: `nvm install 22` veya NodeSource.

## Kurulum (ilk kullanım)

1. https://www.etsy.com/developers/apps → app'in **keystring** (`apiKey`) ve **shared secret** (`sharedSecret`) değerlerini al. Yeni app kaydında callback URL olarak `http://localhost:3737/callback` gir.
2. `~/.etsy-mcp/config.json` oluştur:
```json
{ "apiKey": "keystring...", "sharedSecret": "sharedsecret..." }
```
(`ETSY_API_KEY` / `ETSY_SHARED_SECRET` ortam değişkenleri önceliklidir.)
3. Sohbette "Etsy'ye bağlan" de → `authenticate` aracı OAuth onay URL'ini döndürür. Bir kez onayla; tokenlar `~/.etsy-mcp/tokens.json`'a kaydedilir ve otomatik yenilenir.

### Headless sunucular / VPS

`authenticate` aracı onay URL'ini doğrudan sohbete döndürür ve beklemez. Sunucuda
tarayıcı yoksa callback portunu SSH ile kendi bilgisayarına taşı:

```bash
ssh -L 3737:localhost:3737 user@vps
```

`ETSY_NO_BROWSER=1` ortam değişkeni tarayıcı açma denemesini tamamen kapatır.
Alternatif: halihazırda yetkili bir makineden `~/.etsy-mcp/tokens.json` dosyasını
kopyalarsan OAuth'u tamamen atlarsın (tokenlar makineye bağlı değildir; dosya
hassastır, güvenli taşı).

## VPS / Linux üzerinde zcode CLI ile

```bash
# yerel makineden:
rsync -a --exclude '.video_agent' --exclude '.git' /c/Projects/etsyplugin/ user@vps:~/etsyplugin/
# veya GitHub marketplace'i VPS'teki ZCode'a ekle (Settings → Plugin Management → Discover → +)
```

1. Node.js ≥ 20 kur.
2. Marketplace'i ekle (repo URL'si veya klasör), **Personal** altından **etsy-manager → Install**.
3. `~/.etsy-mcp/config.json` dosyasını oluştur (yukarıda).
4. "Etsy'ye bağlan" de → SSH tüneli + yerel tarayıcı ile onayla (veya token kopyala).

## Vendor'a uygulanan yamalar

- OAuth callback portu sabitlendi (varsayılan 3737, `ETSY_CALLBACK_PORT` ile değiştirilebilir).
- Kimlik bilgileri `~/.etsy-mcp/config.json` üzerinden okunur (env değişkenleri önceliklidir).
- Headless/VPS desteği: onay URL'i tarayıcı açılmadan önce hem stderr'e yazılır hem de
`authenticate` sonucunda döner; araç bloklamaz; tarayıcı açma hatası akışı bozmaz;
`ETSY_NO_BROWSER=1` tarayıcı denemesini kapatır.
- Callback sunucusu yalnızca `127.0.0.1`'e bağlanır (SSH tüneliyle uyumlu, VPS'te dışa kapalı);
port doluysa net hata verir.

## Yan etkiler

- Yerel bir MCP server (`node scripts/mcp-server.mjs`) çalıştırır; `api.etsy.com` /
`www.etsy.com` adreslerine HTTPS istekleri yapar.
- OAuth sırasında en fazla 2 dakika `127.0.0.1:3737`'de geçici bir HTTP sunucusu açar
ve onay URL'ini tarayıcıda açmayı dener (`ETSY_NO_BROWSER=1` ile atlanır).
- Yalnızca `~/.etsy-mcp/` içine yazar (senin oluşturduğun `config.json`, yönetilen
`tokens.json`). Başka hiçbir dosyaya dokunmaz.

## Yapı

```
.zcode-plugin/plugin.json plugin manifest
.mcp.json MCP server tanımı (node scripts/mcp-server.mjs)
scripts/mcp-server.mjs kimlik okuyan launcher
skills/etsy-manager/SKILL.md kullanım kılavuzu
vendor/etsy-mcp/server-bundle.mjs bağımlılıksız MCP sunucusu (esbuild bundle)
vendor/etsy-mcp/src/ yamalanmış upstream TypeScript kaynakları
```

## Lisans

MIT — Copyright (c) 2026 [Hugo](https://gozutok.info). Vendor'lanmış
[aserper/etsy-mcp](https://github.com/aserper/etsy-mcp) MIT lisanslıdır
([lisansı](./vendor/etsy-mcp/LICENSE)).
74 changes: 74 additions & 0 deletions external_plugins/etsy-manager/scripts/mcp-server.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
#!/usr/bin/env node
/**
* Launcher for the vendored etsy-mcp server (github.com/aserper/etsy-mcp, MIT),
* bundled with esbuild into a dependency-free server-bundle.mjs.
*
* Reads Etsy credentials from (first match wins):
* 1. Real environment variables ETSY_API_KEY / ETSY_SHARED_SECRET
* 2. ~/.etsy-mcp/config.json -> { "apiKey": "...", "sharedSecret": "...", "callbackPort": 3737 }
*
* Then spawns vendor/etsy-mcp/dist/index.js with those variables injected.
* OAuth tokens are stored by the server itself at ~/.etsy-mcp/tokens.json.
*/
import { spawn } from "node:child_process";
import { existsSync, readFileSync } from "node:fs";
import { join } from "node:path";
import { homedir } from "node:os";
import { fileURLToPath } from "node:url";

const pluginRoot = join(fileURLToPath(import.meta.url), "..", "..");
const serverEntry = join(pluginRoot, "vendor", "etsy-mcp", "server-bundle.mjs");
const userConfigPath = join(homedir(), ".etsy-mcp", "config.json");

const env = { ...process.env };

if (!env.ETSY_API_KEY || !env.ETSY_SHARED_SECRET) {
if (existsSync(userConfigPath)) {
try {
const cfg = JSON.parse(readFileSync(userConfigPath, "utf8"));
if (cfg.apiKey && !env.ETSY_API_KEY) env.ETSY_API_KEY = cfg.apiKey;
if (cfg.sharedSecret && !env.ETSY_SHARED_SECRET) env.ETSY_SHARED_SECRET = cfg.sharedSecret;
if (cfg.callbackPort) env.ETSY_CALLBACK_PORT = String(cfg.callbackPort);
} catch (err) {
console.error(`[etsy-manager] Could not parse ${userConfigPath}: ${err.message}`);
process.exit(1);
}
}
}

if (!env.ETSY_API_KEY || !env.ETSY_SHARED_SECRET) {
console.error(
[
"[etsy-manager] Etsy credentials not found.",
"",
"Etsy urunlerinizi yonetebilmek icin once su adimlari yapin / To configure:",
"",
`1. Create the file: ${userConfigPath}`,
'2. Content: { "apiKey": "ETSY_KEYSTRING", "sharedSecret": "ETSY_SHARED_SECRET" }',
"3. Get both values from your app at https://www.etsy.com/developers/apps",
" (register a new app if needed; callback URL: http://localhost:3737/callback)",
"4. Restart this MCP server (reload the plugin / restart ZCode).",
"",
"Then run the 'authenticate' tool once to complete Etsy OAuth in a browser.",
].join("\n")
);
process.exit(1);
}

if (!existsSync(serverEntry)) {
console.error(`[etsy-manager] Vendored server not found: ${serverEntry}`);
process.exit(1);
}

const child = spawn(process.execPath, [serverEntry], {
env,
stdio: ["inherit", "inherit", "inherit"],
windowsHide: true,
});

for (const sig of ["SIGINT", "SIGTERM"]) {
process.on(sig, () => {
child.kill(sig);
});
}
child.on("exit", (code) => process.exit(code ?? 1));
Loading