@justmpm/mmx-cli 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +37 -0
- package/README.md +13 -15
- package/dist/index.d.ts +7 -5
- package/dist/index.js +7 -6
- package/dist/tools/image-generate.js +22 -7
- package/dist/tools/index.js +1 -2
- package/dist/types.d.ts +5 -3
- package/dist/utils.d.ts +8 -2
- package/dist/utils.js +94 -18
- package/package.json +1 -1
- package/dist/tools/auth.d.ts +0 -30
- package/dist/tools/auth.js +0 -137
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,42 @@ e este projeto segue [Semantic Versioning](https://semver.org/lang/pt-BR/).
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.1.2] - 2026-06-29
|
|
11
|
+
|
|
12
|
+
### Documentação
|
|
13
|
+
|
|
14
|
+
- README.md, CLAUDE.md, AGENTS.md: removidas referências às 3 tools de auth excluídas na 0.1.1.
|
|
15
|
+
- Seção "Como Funciona" do README atualizada com `buildCmdLine()` + `shell:true` no Windows.
|
|
16
|
+
- Mensagem de auth no README clarifica que MCP não tem tool de auth.
|
|
17
|
+
|
|
18
|
+
## [0.1.1] - 2026-06-29
|
|
19
|
+
|
|
20
|
+
### Removido
|
|
21
|
+
|
|
22
|
+
- **3 tools de auth removidas**: `mmx_login`, `mmx_auth_status`, `mmx_quota_show`. Cada usuário se autentica no próprio `mmx` CLI local (via `MINIMAX_API_KEY` ou `mmx auth login`); o MCP herda via `spawn`. **Total: 15 → 12 tools.**
|
|
23
|
+
- Arquivo `src/tools/auth.ts` deletado.
|
|
24
|
+
- Mensagens de erro agora orientam o usuário a autenticar localmente (`mmx auth login` ou env var) em vez de chamar tool inexistente.
|
|
25
|
+
|
|
26
|
+
### Corrigido
|
|
27
|
+
|
|
28
|
+
- **Windows spawn ENOENT**: binários npm (`.cmd`/`.bat`) só rodam via `cmd.exe`, mas `spawn` com `shell:false` falha em `.cmd`. Solução: nova função `buildCmdLine()` que monta command-line string único + `shell:true` no Windows. Sem isso, `\\` em paths era consumido pelo CMD (`C:\Users\...` virava `C:Users...`) e tools como `mmx_music_generate` travavam silenciosamente.
|
|
29
|
+
- **`mmx_image_generate` parsing**: CLI retorna JSON `{"saved": ["path1", ...]}` em `--quiet mode`, mas o handler fazia `split(/\r?\n/)` esperando paths em linhas separadas. Agora parseia JSON com fallback para line-split.
|
|
30
|
+
- **`MmxInvocation.command`**: tipo literal `"mmx" | "npx"` mudou para `string` para suportar path absoluto `.cmd` no Windows.
|
|
31
|
+
|
|
32
|
+
### Adicionado
|
|
33
|
+
|
|
34
|
+
- Função `buildCmdLine()` em `src/utils.ts` para escapar args corretamente no Windows (cmd.exe).
|
|
35
|
+
|
|
36
|
+
### Segurança
|
|
37
|
+
|
|
38
|
+
- Validação de caminhos/path traversal mantida em todos os tools.
|
|
39
|
+
- Concurrency limiter preservado.
|
|
40
|
+
|
|
41
|
+
### Testes
|
|
42
|
+
|
|
43
|
+
- **81 testes Vitest passando** (eram 83 — removidos 2 testes específicos das tools de auth).
|
|
44
|
+
- Novo teste: registry de tools agora exige **exatamente 12** e verifica ausência das 3 tools de auth.
|
|
45
|
+
|
|
10
46
|
## [0.1.0] - 2026-06-29
|
|
11
47
|
|
|
12
48
|
### Adicionado
|
|
@@ -49,4 +85,5 @@ e este projeto segue [Semantic Versioning](https://semver.org/lang/pt-BR/).
|
|
|
49
85
|
- Padrões expandidos de detecção de quota (spending limit, insufficient balance, etc)
|
|
50
86
|
|
|
51
87
|
[Unreleased]: https://github.com/Just-mpm
|
|
88
|
+
[0.1.1]: https://github.com/Just-mpm
|
|
52
89
|
[0.1.0]: https://github.com/Just-mpm
|
package/README.md
CHANGED
|
@@ -6,15 +6,14 @@ MCP Server que expõe o CLI `mmx` (MiniMax) como **tools para IAs usarem diretam
|
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
-
## Tools Disponíveis (
|
|
9
|
+
## Tools Disponíveis (12)
|
|
10
10
|
|
|
11
11
|
Todas as tools que geram arquivos de mídia exigem `outputDir` (obrigatório) — o usuário controla onde salvar, MCP não acumula temp files.
|
|
12
12
|
|
|
13
|
+
> **Auth é responsabilidade do usuário**: cada um se autentica no próprio `mmx` CLI local (via env `MINIMAX_API_KEY` ou `mmx auth login`). O MCP herda essas credenciais via `spawn` — não há tool de auth no MCP.
|
|
14
|
+
|
|
13
15
|
| Tool | Content type | Descrição |
|
|
14
16
|
|---|---|---|
|
|
15
|
-
| `mmx_login` | `text` | Autentica no mmx-cli via env `MINIMAX_API_KEY` ou `apiKey` na chamada |
|
|
16
|
-
| `mmx_auth_status` | `text` | Mostra método de auth ativo |
|
|
17
|
-
| `mmx_quota_show` | `text` | Exibe uso de Token Plan (janelas 5h/semanal) |
|
|
18
17
|
| `mmx_image_generate` | `resource_link` (.png/.jpg/.webp) | Gera imagem a partir de prompt — salva em `outputDir` |
|
|
19
18
|
| `mmx_speech_synthesize` | `resource_link` (.mp3/.wav) | TTS com voz customizada (`VozMatheusKodaPython`) — salva em `outputDir` |
|
|
20
19
|
| `mmx_speech_voices` | `text` | Lista vozes TTS disponíveis |
|
|
@@ -49,9 +48,12 @@ mmx --version # deve retornar 1.0.0+
|
|
|
49
48
|
# Opção A: variável de ambiente (recomendado)
|
|
50
49
|
export MINIMAX_API_KEY="sk-xxxxxxxxxxxxxxxxxxxx"
|
|
51
50
|
|
|
52
|
-
# Opção B:
|
|
51
|
+
# Opção B: login interativo local
|
|
52
|
+
mmx auth login --api-key "sk-xxxxxxxxxxxxxxxxxxxx"
|
|
53
53
|
```
|
|
54
54
|
|
|
55
|
+
> O MCP **não tem** tool de auth — cada usuário se autentica no próprio `mmx` CLI localmente. O MCP herda as credenciais via `spawn`.
|
|
56
|
+
|
|
55
57
|
### 3. Instalar o MCP Server
|
|
56
58
|
|
|
57
59
|
**Opção A — Global (recomendado):**
|
|
@@ -123,7 +125,7 @@ O MCP Server:
|
|
|
123
125
|
|
|
124
126
|
1. Recebe JSON-RPC via stdio (do cliente MCP)
|
|
125
127
|
2. Converte para args do CLI `mmx`
|
|
126
|
-
3. Faz `child_process.spawn('mmx', args)` (com fallback para `npx mmx-cli`)
|
|
128
|
+
3. Faz `child_process.spawn('mmx', args)` — no Windows usa `buildCmdLine()` + `shell:true` para evitar que `cmd.exe` quebre paths com `\\` (com fallback para `npx mmx-cli`)
|
|
127
129
|
4. Captura stdout/stderr e converte para MCP content blocks:
|
|
128
130
|
- Imagens (`mmx image generate`) → `resource_link` (file URI no `outputDir` do user)
|
|
129
131
|
- Áudio (`mmx speech synthesize`, `mmx music generate`) → `resource_link` (file URI no `outputDir` do user)
|
|
@@ -157,11 +159,8 @@ Músicas?
|
|
|
157
159
|
Buscar na web?
|
|
158
160
|
└─► mmx_search_query → text
|
|
159
161
|
|
|
160
|
-
|
|
161
|
-
└─►
|
|
162
|
-
|
|
163
|
-
Erro 401/não-autenticado?
|
|
164
|
-
└─► mmx_login
|
|
162
|
+
Upload de arquivo (para vision_describe / video_generate)?
|
|
163
|
+
└─► mmx_file_upload → file_id
|
|
165
164
|
```
|
|
166
165
|
|
|
167
166
|
---
|
|
@@ -216,19 +215,18 @@ npm run typecheck # tsc --noEmit
|
|
|
216
215
|
mmx-cli/
|
|
217
216
|
├── src/
|
|
218
217
|
│ ├── index.ts # Servidor MCP principal
|
|
219
|
-
│ ├── utils.ts # runMmx, findInPath, isAuthError, checkMmxVersion
|
|
218
|
+
│ ├── utils.ts # runMmx, buildCmdLine, findInPath, isAuthError, checkMmxVersion
|
|
220
219
|
│ ├── media.ts # Helpers para content types MCP
|
|
221
220
|
│ ├── types.ts # Re-exports dos tipos do MCP SDK
|
|
222
221
|
│ ├── version.ts # Constantes (min mmx version)
|
|
223
222
|
│ └── tools/
|
|
224
|
-
│ ├── auth.ts # mmx_login, mmx_auth_status, mmx_quota_show
|
|
225
223
|
│ ├── image-generate.ts # mmx_image_generate
|
|
226
224
|
│ ├── speech-synthesize.ts
|
|
227
225
|
│ ├── video.ts # mmx_video_generate, mmx_video_task_get
|
|
228
|
-
│ ├── text-music-vision.ts # text_chat, music_generate, vision, search
|
|
226
|
+
│ ├── text-music-vision.ts # text_chat, music_generate, vision, search, voices
|
|
229
227
|
│ ├── files.ts # file_upload, file_list, file_delete
|
|
230
228
|
│ └── index.ts # Barrel export
|
|
231
|
-
├── test/ # Vitest (
|
|
229
|
+
├── test/ # Vitest (81 testes)
|
|
232
230
|
└── dist/ # Build output
|
|
233
231
|
```
|
|
234
232
|
|
package/dist/index.d.ts
CHANGED
|
@@ -2,11 +2,10 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* @justmpm/mmx-cli - MCP Server para MiniMax CLI (`mmx`)
|
|
4
4
|
*
|
|
5
|
-
* Expõe
|
|
6
|
-
* -
|
|
7
|
-
* -
|
|
8
|
-
* -
|
|
9
|
-
* - Vídeo: mmx_video_generate (resource link polling), mmx_video_task_get
|
|
5
|
+
* Expõe 12 tools que wrappam comandos do CLI `mmx` instalado globalmente:
|
|
6
|
+
* - Imagem: mmx_image_generate
|
|
7
|
+
* - Áudio: mmx_speech_synthesize, mmx_speech_voices
|
|
8
|
+
* - Vídeo: mmx_video_generate (poll+download), mmx_video_task_get
|
|
10
9
|
* - LLM: mmx_text_chat
|
|
11
10
|
* - Música: mmx_music_generate
|
|
12
11
|
* - Visão: mmx_vision_describe
|
|
@@ -14,6 +13,9 @@
|
|
|
14
13
|
* - Files: mmx_file_upload, mmx_file_list, mmx_file_delete
|
|
15
14
|
*
|
|
16
15
|
* Zero dependência de API — faz spawn do CLI `mmx` via child_process.
|
|
16
|
+
* Auth fica fora: cada usuário se autentica no próprio `mmx` CLI local
|
|
17
|
+
* (config.json ou MINIMAX_API_KEY), e o MCP herda essas credenciais via spawn.
|
|
18
|
+
*
|
|
17
19
|
* Zod v4 nativo (z.toJSONSchema() substitui zod-to-json-schema).
|
|
18
20
|
*
|
|
19
21
|
* @see https://modelcontextprotocol.io/
|
package/dist/index.js
CHANGED
|
@@ -2,11 +2,10 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* @justmpm/mmx-cli - MCP Server para MiniMax CLI (`mmx`)
|
|
4
4
|
*
|
|
5
|
-
* Expõe
|
|
6
|
-
* -
|
|
7
|
-
* -
|
|
8
|
-
* -
|
|
9
|
-
* - Vídeo: mmx_video_generate (resource link polling), mmx_video_task_get
|
|
5
|
+
* Expõe 12 tools que wrappam comandos do CLI `mmx` instalado globalmente:
|
|
6
|
+
* - Imagem: mmx_image_generate
|
|
7
|
+
* - Áudio: mmx_speech_synthesize, mmx_speech_voices
|
|
8
|
+
* - Vídeo: mmx_video_generate (poll+download), mmx_video_task_get
|
|
10
9
|
* - LLM: mmx_text_chat
|
|
11
10
|
* - Música: mmx_music_generate
|
|
12
11
|
* - Visão: mmx_vision_describe
|
|
@@ -14,6 +13,9 @@
|
|
|
14
13
|
* - Files: mmx_file_upload, mmx_file_list, mmx_file_delete
|
|
15
14
|
*
|
|
16
15
|
* Zero dependência de API — faz spawn do CLI `mmx` via child_process.
|
|
16
|
+
* Auth fica fora: cada usuário se autentica no próprio `mmx` CLI local
|
|
17
|
+
* (config.json ou MINIMAX_API_KEY), e o MCP herda essas credenciais via spawn.
|
|
18
|
+
*
|
|
17
19
|
* Zod v4 nativo (z.toJSONSchema() substitui zod-to-json-schema).
|
|
18
20
|
*
|
|
19
21
|
* @see https://modelcontextprotocol.io/
|
|
@@ -153,7 +155,6 @@ CONFIGURACAO no Claude Desktop (claude_desktop_config.json):
|
|
|
153
155
|
}
|
|
154
156
|
|
|
155
157
|
TOOLS MCP (${TOOLS.length}):
|
|
156
|
-
Auth: mmx_login, mmx_auth_status, mmx_quota_show
|
|
157
158
|
Midia: mmx_image_generate, mmx_speech_synthesize, mmx_speech_voices
|
|
158
159
|
Video: mmx_video_generate (poll+download), mmx_video_task_get
|
|
159
160
|
LLM: mmx_text_chat
|
|
@@ -22,7 +22,7 @@ export const description = "Gera uma ou mais imagens a partir de prompt usando o
|
|
|
22
22
|
"Retorna resource_link com .png/.jpg/.webp via file URI. " +
|
|
23
23
|
"Rate limit: 10 RPM. Custo: ~$0.0035/imagem. " +
|
|
24
24
|
"Para descrever/analisar imagem existente use mmx_vision_describe. " +
|
|
25
|
-
"Para batch grande (>20 imagens),
|
|
25
|
+
"Para batch grande (>20 imagens), rode `mmx quota show` localmente antes. " +
|
|
26
26
|
"**Não substitui** mmx_vision_describe (gera, não analisa).";
|
|
27
27
|
export const inputSchema = z.object({
|
|
28
28
|
outputDir: z
|
|
@@ -137,12 +137,27 @@ export async function handler(args) {
|
|
|
137
137
|
if (result.exitCode !== 0) {
|
|
138
138
|
return handleToolError("gerar imagem", result);
|
|
139
139
|
}
|
|
140
|
-
//
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
140
|
+
// O CLI retorna JSON `{"saved": ["path1", "path2", ...]}` em --quiet mode.
|
|
141
|
+
// Fallback: tentar parsear como paths um por linha (caso CLI mude o formato).
|
|
142
|
+
let savedPaths = [];
|
|
143
|
+
const trimmed = result.stdout.trim();
|
|
144
|
+
if (trimmed.startsWith("{")) {
|
|
145
|
+
try {
|
|
146
|
+
const parsed = JSON.parse(trimmed);
|
|
147
|
+
if (Array.isArray(parsed.saved)) {
|
|
148
|
+
savedPaths = parsed.saved.filter((p) => typeof p === "string");
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
// cai no fallback abaixo
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
if (savedPaths.length === 0) {
|
|
156
|
+
savedPaths = trimmed
|
|
157
|
+
.split(/\r?\n/)
|
|
158
|
+
.filter(Boolean)
|
|
159
|
+
.map((p) => (isAbsolute(p) ? p : join(outputDir, basename(p))));
|
|
160
|
+
}
|
|
146
161
|
if (savedPaths.length === 0) {
|
|
147
162
|
return {
|
|
148
163
|
content: [
|
package/dist/tools/index.js
CHANGED
|
@@ -4,14 +4,13 @@
|
|
|
4
4
|
* Cada módulo de domínio exporta um array `*Tools: McpTool[]`.
|
|
5
5
|
* Este barrel agrega todos em uma lista flat consumida pelo `index.ts`.
|
|
6
6
|
*/
|
|
7
|
-
import { authTools } from "./auth.js";
|
|
8
7
|
import { imageGenerateTool } from "./image-generate.js";
|
|
9
8
|
import { speechSynthesizeTool } from "./speech-synthesize.js";
|
|
10
9
|
import { videoTools } from "./video.js";
|
|
11
10
|
import { llmAuxTools } from "./text-music-vision.js";
|
|
12
11
|
import { fileTools } from "./files.js";
|
|
12
|
+
// 12 tools — auth removido (cada usuário autentica no próprio mmx CLI local).
|
|
13
13
|
export const TOOLS = [
|
|
14
|
-
...authTools,
|
|
15
14
|
imageGenerateTool,
|
|
16
15
|
speechSynthesizeTool,
|
|
17
16
|
...videoTools,
|
package/dist/types.d.ts
CHANGED
|
@@ -19,7 +19,7 @@ export type ToolContent = TextContent | ImageContent | AudioContent | ResourceLi
|
|
|
19
19
|
* Cada módulo de domínio exporta `*Tools: McpTool[]` que o servidor concatena.
|
|
20
20
|
*/
|
|
21
21
|
export interface McpTool {
|
|
22
|
-
/** Nome único (ex: '
|
|
22
|
+
/** Nome único (ex: 'mmx_image_generate'). */
|
|
23
23
|
name: string;
|
|
24
24
|
/** Descrição legível — vai pra `description` no listTools. */
|
|
25
25
|
description: string;
|
|
@@ -42,11 +42,13 @@ export interface SpawnResult {
|
|
|
42
42
|
/**
|
|
43
43
|
* Como invocar o binário do mmx. Resolvido no boot via `resolveMmxInvocation`.
|
|
44
44
|
*
|
|
45
|
-
* - `command: "mmx"` → uso direto (
|
|
45
|
+
* - `command: "mmx"` → uso direto via PATH (funciona em Unix)
|
|
46
46
|
* - `command: "npx"` → `prefixArgs` carrega `-y mmx-cli mmx` (fallback)
|
|
47
|
+
* - `command: "<path-absoluto>"` → no Windows preferimos o caminho
|
|
48
|
+
* absoluto do `mmx.cmd` para evitar problemas de PATHEXT no `spawn`.
|
|
47
49
|
*/
|
|
48
50
|
export interface MmxInvocation {
|
|
49
|
-
command:
|
|
51
|
+
command: string;
|
|
50
52
|
prefixArgs: string[];
|
|
51
53
|
}
|
|
52
54
|
/**
|
package/dist/utils.d.ts
CHANGED
|
@@ -28,6 +28,12 @@ export declare function findInPath(command: string): string | null;
|
|
|
28
28
|
* 2. `npx -y mmx-cli` (sempre funciona em qualquer máquina com Node ≥ 18)
|
|
29
29
|
*
|
|
30
30
|
* O resultado é cacheado em memória para evitar resolução repetida.
|
|
31
|
+
*
|
|
32
|
+
* IMPORTANTE (Windows): os binários instalados via npm global são arquivos
|
|
33
|
+
* `.cmd` (batch scripts), não `.exe`. `child_process.spawn(cmd, ..., { shell: false })`
|
|
34
|
+
* falha com ENOENT nesse caso. Por isso, `runMmxInner`/`checkMmxVersion`
|
|
35
|
+
* ativam `shell: true` quando `process.platform === "win32"`, permitindo
|
|
36
|
+
* que o cmd.exe resolva os `.cmd` via PATHEXT.
|
|
31
37
|
*/
|
|
32
38
|
export declare function resolveMmxInvocation(): MmxInvocation;
|
|
33
39
|
/**
|
|
@@ -89,8 +95,8 @@ export declare function isTimeoutError(message: string): boolean;
|
|
|
89
95
|
*/
|
|
90
96
|
export declare function isFileNotFound(message: string): boolean;
|
|
91
97
|
/**
|
|
92
|
-
* Mensagem para erros de autenticação. Direciona
|
|
93
|
-
*
|
|
98
|
+
* Mensagem para erros de autenticação. Direciona o usuário a autenticar
|
|
99
|
+
* no próprio CLI `mmx` (cada usuário é responsável pela sua credencial local).
|
|
94
100
|
*/
|
|
95
101
|
export declare function authErrorMessage(detail: string): string;
|
|
96
102
|
/**
|
package/dist/utils.js
CHANGED
|
@@ -58,12 +58,38 @@ export function findInPath(command) {
|
|
|
58
58
|
* 2. `npx -y mmx-cli` (sempre funciona em qualquer máquina com Node ≥ 18)
|
|
59
59
|
*
|
|
60
60
|
* O resultado é cacheado em memória para evitar resolução repetida.
|
|
61
|
+
*
|
|
62
|
+
* IMPORTANTE (Windows): os binários instalados via npm global são arquivos
|
|
63
|
+
* `.cmd` (batch scripts), não `.exe`. `child_process.spawn(cmd, ..., { shell: false })`
|
|
64
|
+
* falha com ENOENT nesse caso. Por isso, `runMmxInner`/`checkMmxVersion`
|
|
65
|
+
* ativam `shell: true` quando `process.platform === "win32"`, permitindo
|
|
66
|
+
* que o cmd.exe resolva os `.cmd` via PATHEXT.
|
|
61
67
|
*/
|
|
62
68
|
export function resolveMmxInvocation() {
|
|
63
69
|
if (cachedInvocation)
|
|
64
70
|
return cachedInvocation;
|
|
65
71
|
const mmxPath = findInPath("mmx");
|
|
66
72
|
if (mmxPath) {
|
|
73
|
+
// No Windows, findInPath retorna "C:\...\npm\mmx" (sem extensão) ou
|
|
74
|
+
// "C:\...\npm\mmx.cmd". Para garantir que o spawn resolva corretamente
|
|
75
|
+
// o script batch, preferimos o caminho com ".cmd" quando existir.
|
|
76
|
+
const isWindows = process.platform === "win32";
|
|
77
|
+
if (isWindows) {
|
|
78
|
+
const cmdPath = mmxPath.endsWith(".cmd")
|
|
79
|
+
? mmxPath
|
|
80
|
+
: `${mmxPath}.cmd`;
|
|
81
|
+
// Se o .cmd existir, usamos o path absoluto. Caso contrário, caímos
|
|
82
|
+
// pro nome "mmx" (vai depender do PATHEXT + shell do spawn).
|
|
83
|
+
try {
|
|
84
|
+
if (existsSync(cmdPath)) {
|
|
85
|
+
cachedInvocation = { command: cmdPath, prefixArgs: [] };
|
|
86
|
+
return cachedInvocation;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
// best-effort
|
|
91
|
+
}
|
|
92
|
+
}
|
|
67
93
|
cachedInvocation = { command: "mmx", prefixArgs: [] };
|
|
68
94
|
}
|
|
69
95
|
else {
|
|
@@ -93,6 +119,35 @@ export function _resetMmxInvocationCache() {
|
|
|
93
119
|
export function runMmx(args, options = {}) {
|
|
94
120
|
return withConcurrency(() => runMmxInner(args, options));
|
|
95
121
|
}
|
|
122
|
+
/**
|
|
123
|
+
* Constrói command-line string a partir de um array de args, escapando
|
|
124
|
+
* corretamente para invocação via `cmd.exe /c` no Windows.
|
|
125
|
+
*
|
|
126
|
+
* IMPORTANTE: no Windows, `spawn(cmd, [args], { shell: true })` faz Node
|
|
127
|
+
* concatenar os args numa string SEM escapar `\\`. Quando args contêm
|
|
128
|
+
* paths Windows (com `\\`), o cmd.exe trata `\\` como escape e o path
|
|
129
|
+
* chega quebrado (ex: `C:\Users\...` vira `C:Users...`).
|
|
130
|
+
*
|
|
131
|
+
* Solução: usar shell:true + command-line string única já formatada
|
|
132
|
+
* (com aspas duplas nos args que têm espaço). Aí o cmd.exe recebe
|
|
133
|
+
* exatamente o que pretendemos.
|
|
134
|
+
*
|
|
135
|
+
* Args com aspas duplas ou backslashes precisam de tratamento adicional,
|
|
136
|
+
* mas como todos os args aqui vêm de validação Zod (sem raw user shell
|
|
137
|
+
* input) e paths vêm de `path.join()` (sem aspas), o risco de injection
|
|
138
|
+
* é mínimo.
|
|
139
|
+
*/
|
|
140
|
+
function buildCmdLine(command, args) {
|
|
141
|
+
const quoteIfNeeded = (a) => {
|
|
142
|
+
// Se contém espaço, aspas duplas ou outros chars que CMD interpreta
|
|
143
|
+
if (/[\s"&|<>^()%!]/.test(a)) {
|
|
144
|
+
// Escapar aspas duplas internas duplicando-as (regra CMD)
|
|
145
|
+
return `"${a.replace(/"/g, '""')}"`;
|
|
146
|
+
}
|
|
147
|
+
return a;
|
|
148
|
+
};
|
|
149
|
+
return [command, ...args].map(quoteIfNeeded).join(" ");
|
|
150
|
+
}
|
|
96
151
|
/**
|
|
97
152
|
* Implementação interna de `runMmx` (sem concurrency limit).
|
|
98
153
|
* Usada por `runMmx` (com limit) e por testes.
|
|
@@ -102,19 +157,31 @@ export function runMmxInner(args, options = {}) {
|
|
|
102
157
|
const fullArgs = [...invocation.prefixArgs, ...args];
|
|
103
158
|
const timeoutMs = options.timeoutMs ?? 300_000;
|
|
104
159
|
const signal = options.signal;
|
|
160
|
+
const isWindows = process.platform === "win32";
|
|
105
161
|
return new Promise((resolve) => {
|
|
106
162
|
// Se signal já abortado, retornar imediatamente
|
|
107
163
|
if (signal?.aborted) {
|
|
108
164
|
resolve({ stdout: "", stderr: "[mmx-cli MCP] Operação cancelada antes de iniciar.", exitCode: 1 });
|
|
109
165
|
return;
|
|
110
166
|
}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
167
|
+
// No Windows, binários npm (mmx.cmd, npx.cmd) só rodam via cmd.exe.
|
|
168
|
+
// Como `spawn(cmd, args, { shell: true })` deixa Node concatenar args
|
|
169
|
+
// SEM escapar `\\`, usamos command-line string único + shell:true.
|
|
170
|
+
// Em Unix, mantemos shell:false (não concatena) e args como array.
|
|
171
|
+
const proc = isWindows
|
|
172
|
+
? spawn(buildCmdLine(invocation.command, fullArgs), [], {
|
|
173
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
174
|
+
shell: true,
|
|
175
|
+
cwd: options.cwd,
|
|
176
|
+
env: { ...process.env, ...options.env },
|
|
177
|
+
windowsHide: true,
|
|
178
|
+
})
|
|
179
|
+
: spawn(invocation.command, fullArgs, {
|
|
180
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
181
|
+
shell: false,
|
|
182
|
+
cwd: options.cwd,
|
|
183
|
+
env: { ...process.env, ...options.env },
|
|
184
|
+
});
|
|
118
185
|
let stdout = "";
|
|
119
186
|
let stderr = "";
|
|
120
187
|
let resolved = false;
|
|
@@ -345,15 +412,16 @@ export function isFileNotFound(message) {
|
|
|
345
412
|
// Mensagens de erro padronizadas
|
|
346
413
|
// =============================================================================
|
|
347
414
|
/**
|
|
348
|
-
* Mensagem para erros de autenticação. Direciona
|
|
349
|
-
*
|
|
415
|
+
* Mensagem para erros de autenticação. Direciona o usuário a autenticar
|
|
416
|
+
* no próprio CLI `mmx` (cada usuário é responsável pela sua credencial local).
|
|
350
417
|
*/
|
|
351
418
|
export function authErrorMessage(detail) {
|
|
352
419
|
return [
|
|
353
420
|
"Erro de autenticação. A sessão do mmx-cli expirou ou a API key é inválida.",
|
|
354
|
-
"Para resolver:",
|
|
355
|
-
" 1. Defina a variável de ambiente MINIMAX_API_KEY com uma chave Pay-as-you-go (sk-...)
|
|
356
|
-
" 2.
|
|
421
|
+
"Para resolver, no seu terminal:",
|
|
422
|
+
" 1. Defina a variável de ambiente MINIMAX_API_KEY com uma chave Pay-as-you-go (sk-...)",
|
|
423
|
+
" 2. Ou rode `mmx auth login --api-key <sua-chave>` localmente",
|
|
424
|
+
" 3. Reinicie o cliente MCP (Claude Desktop / Cursor) para carregar o novo env",
|
|
357
425
|
"Nota: Use Pay-as-you-go (sk-...), NÃO Token Plan (sk-cp-...). O Token Plan foi feito para uso individual e aplica throttling agressivo em batch.",
|
|
358
426
|
`Detalhes: ${detail}`,
|
|
359
427
|
].join("\n");
|
|
@@ -396,7 +464,7 @@ export function quotaErrorMessage(operation, detail) {
|
|
|
396
464
|
" - Aguarde a janela de rate limit resetar (geralmente 1 minuto para RPM)",
|
|
397
465
|
" - Se for Token Plan (janela 5h), aguarde reset ou faça upgrade",
|
|
398
466
|
" - Se for Pay-as-you-go, verifique saldo em platform.minimax.io",
|
|
399
|
-
" - Para operações caras,
|
|
467
|
+
" - Para operações caras, rode `mmx quota show` localmente antes de fazer batch",
|
|
400
468
|
`Detalhes: ${detail}`,
|
|
401
469
|
].join("\n");
|
|
402
470
|
}
|
|
@@ -518,11 +586,19 @@ export async function checkMmxVersion() {
|
|
|
518
586
|
const args = [...invocation.prefixArgs, "--version"];
|
|
519
587
|
const invocationCommand = invocation.command;
|
|
520
588
|
const stdout = await new Promise((resolve, reject) => {
|
|
521
|
-
const
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
589
|
+
const isWin = process.platform === "win32";
|
|
590
|
+
// Mesma justificativa do runMmxInner: usar command-line string único
|
|
591
|
+
// + shell:true no Windows para evitar que o cmd.exe quebre paths.
|
|
592
|
+
const proc = isWin
|
|
593
|
+
? spawn(buildCmdLine(invocationCommand, args), [], {
|
|
594
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
595
|
+
shell: true,
|
|
596
|
+
windowsHide: true,
|
|
597
|
+
})
|
|
598
|
+
: spawn(invocationCommand, args, {
|
|
599
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
600
|
+
shell: false,
|
|
601
|
+
});
|
|
526
602
|
let out = "";
|
|
527
603
|
proc.stdout.on("data", (chunk) => {
|
|
528
604
|
out += chunk.toString();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@justmpm/mmx-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "MCP Server que expõe o mmx-cli (MiniMax) como tools para IAs usarem diretamente. Spawn do CLI global, content types nativos (image, audio, resource).",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
package/dist/tools/auth.d.ts
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @justmpm/mmx-cli - Tools de autenticação
|
|
3
|
-
*
|
|
4
|
-
* 3 tools relacionadas ao ciclo de auth do `mmx`:
|
|
5
|
-
* - mmx_login: faz login (não-interativo via --api-key)
|
|
6
|
-
* - mmx_auth_status: checa método de auth atual
|
|
7
|
-
* - mmx_quota_show: exibe quotas do Token Plan
|
|
8
|
-
*/
|
|
9
|
-
import { z } from "zod";
|
|
10
|
-
import type { McpTool, ToolResult } from "../types.js";
|
|
11
|
-
export declare const loginName = "mmx_login";
|
|
12
|
-
export declare const loginDescription: string;
|
|
13
|
-
export declare const loginInputSchema: z.ZodObject<{
|
|
14
|
-
apiKey: z.ZodOptional<z.ZodString>;
|
|
15
|
-
region: z.ZodOptional<z.ZodEnum<{
|
|
16
|
-
global: "global";
|
|
17
|
-
cn: "cn";
|
|
18
|
-
}>>;
|
|
19
|
-
}, z.core.$strip>;
|
|
20
|
-
export declare function loginHandler(args: unknown): Promise<ToolResult>;
|
|
21
|
-
export declare const authStatusName = "mmx_auth_status";
|
|
22
|
-
export declare const authStatusDescription: string;
|
|
23
|
-
export declare const authStatusInputSchema: z.ZodObject<{}, z.core.$strip>;
|
|
24
|
-
export declare function authStatusHandler(_args: unknown): Promise<ToolResult>;
|
|
25
|
-
export declare const quotaShowName = "mmx_quota_show";
|
|
26
|
-
export declare const quotaShowDescription: string;
|
|
27
|
-
export declare const quotaShowInputSchema: z.ZodObject<{}, z.core.$strip>;
|
|
28
|
-
export declare function quotaShowHandler(_args: unknown): Promise<ToolResult>;
|
|
29
|
-
export declare const authTools: McpTool[];
|
|
30
|
-
//# sourceMappingURL=auth.d.ts.map
|
package/dist/tools/auth.js
DELETED
|
@@ -1,137 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @justmpm/mmx-cli - Tools de autenticação
|
|
3
|
-
*
|
|
4
|
-
* 3 tools relacionadas ao ciclo de auth do `mmx`:
|
|
5
|
-
* - mmx_login: faz login (não-interativo via --api-key)
|
|
6
|
-
* - mmx_auth_status: checa método de auth atual
|
|
7
|
-
* - mmx_quota_show: exibe quotas do Token Plan
|
|
8
|
-
*/
|
|
9
|
-
import { z } from "zod";
|
|
10
|
-
import { handleToolError, runMmx } from "../utils.js";
|
|
11
|
-
// =============================================================================
|
|
12
|
-
// mmx_login
|
|
13
|
-
// =============================================================================
|
|
14
|
-
export const loginName = "mmx_login";
|
|
15
|
-
export const loginDescription = "Autentica no mmx-cli (MiniMax). " +
|
|
16
|
-
"**Use quando outras tools retornarem 401/403/Unauthorized** — esta é a primeira coisa a tentar. " +
|
|
17
|
-
"Recomendado: defina a variável de ambiente MINIMAX_API_KEY (Pay-as-you-go) e reinicie o cliente MCP — é mais persistente que passar apiKey aqui. " +
|
|
18
|
-
"Se chamar esta tool passando apiKey direto, ela autentica apenas nesta sessão (CLI não persiste em todos os sistemas). " +
|
|
19
|
-
"**Não use Token Plan (sk-cp-...)** — plataforma aplica throttling agressivo em batch. Use Pay-as-you-go (sk-...).";
|
|
20
|
-
export const loginInputSchema = z.object({
|
|
21
|
-
apiKey: z
|
|
22
|
-
.string()
|
|
23
|
-
.optional()
|
|
24
|
-
.describe("API key (sk-xxx). Se fornecida, usa diretamente; do contrário, usa MINIMAX_API_KEY do env."),
|
|
25
|
-
region: z
|
|
26
|
-
.enum(["global", "cn"])
|
|
27
|
-
.optional()
|
|
28
|
-
.describe("Região da API. Default: global."),
|
|
29
|
-
});
|
|
30
|
-
export async function loginHandler(args) {
|
|
31
|
-
const parsed = loginInputSchema.safeParse(args);
|
|
32
|
-
if (!parsed.success) {
|
|
33
|
-
return {
|
|
34
|
-
content: [
|
|
35
|
-
{
|
|
36
|
-
type: "text",
|
|
37
|
-
text: `Erro de validação: ${parsed.error.issues
|
|
38
|
-
.map((i) => `${i.path.join(".")}: ${i.message}`)
|
|
39
|
-
.join("; ")}`,
|
|
40
|
-
},
|
|
41
|
-
],
|
|
42
|
-
isError: true,
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
const cliArgs = ["auth", "login", "--non-interactive", "--quiet"];
|
|
46
|
-
if (parsed.data.region)
|
|
47
|
-
cliArgs.push("--region", parsed.data.region);
|
|
48
|
-
// SECURITY: passa apiKey via env var em vez de --api-key arg.
|
|
49
|
-
// Evita leak em `ps aux` (Linux) ou Get-CimInstance (Windows).
|
|
50
|
-
const env = {};
|
|
51
|
-
if (parsed.data.apiKey)
|
|
52
|
-
env.MINIMAX_API_KEY = parsed.data.apiKey;
|
|
53
|
-
const result = await runMmx(cliArgs, { timeoutMs: 60_000, env });
|
|
54
|
-
if (result.exitCode !== 0) {
|
|
55
|
-
return handleToolError("autenticar no mmx-cli", result);
|
|
56
|
-
}
|
|
57
|
-
const stdout = result.stdout.trim();
|
|
58
|
-
const content = [
|
|
59
|
-
{
|
|
60
|
-
type: "text",
|
|
61
|
-
text: stdout ||
|
|
62
|
-
"Login concluído. A variável de ambiente MINIMAX_API_KEY foi usada (ou a apiKey passada como argumento).",
|
|
63
|
-
},
|
|
64
|
-
{
|
|
65
|
-
type: "text",
|
|
66
|
-
text: "Dica: pra produção, use uma chave Pay-as-you-go (Token Plan não suporta multi-agentes concorrentes).",
|
|
67
|
-
},
|
|
68
|
-
];
|
|
69
|
-
return { content };
|
|
70
|
-
}
|
|
71
|
-
// =============================================================================
|
|
72
|
-
// mmx_auth_status
|
|
73
|
-
// =============================================================================
|
|
74
|
-
export const authStatusName = "mmx_auth_status";
|
|
75
|
-
export const authStatusDescription = "Mostra o método de autenticação ativo no mmx-cli (api-key, oauth, etc) e a source (config.json, env, flag). " +
|
|
76
|
-
"**Use antes de operações pesadas** (geração em batch, vídeo) pra confirmar que está autenticado. " +
|
|
77
|
-
"**Se retornar erro de auth**, chame mmx_login em seguida.";
|
|
78
|
-
export const authStatusInputSchema = z.object({});
|
|
79
|
-
export async function authStatusHandler(_args) {
|
|
80
|
-
const result = await runMmx(["auth", "status", "--output", "json"]);
|
|
81
|
-
if (result.exitCode !== 0) {
|
|
82
|
-
return handleToolError("checar status de autenticação", result);
|
|
83
|
-
}
|
|
84
|
-
// Tenta parsear pra apresentar formatado; senão, retorna raw.
|
|
85
|
-
let pretty = result.stdout;
|
|
86
|
-
try {
|
|
87
|
-
const parsed = JSON.parse(result.stdout);
|
|
88
|
-
const method = parsed.method ?? "?";
|
|
89
|
-
const source = parsed.source ?? "?";
|
|
90
|
-
const hasKey = typeof parsed.key === "string";
|
|
91
|
-
pretty = `Método: ${method}\nSource: ${source}\nAPI key presente: ${hasKey ? "sim" : "não"}`;
|
|
92
|
-
}
|
|
93
|
-
catch {
|
|
94
|
-
// mantém stdout cru
|
|
95
|
-
}
|
|
96
|
-
return { content: [{ type: "text", text: pretty }] };
|
|
97
|
-
}
|
|
98
|
-
// =============================================================================
|
|
99
|
-
// mmx_quota_show
|
|
100
|
-
// =============================================================================
|
|
101
|
-
export const quotaShowName = "mmx_quota_show";
|
|
102
|
-
export const quotaShowDescription = "Exibe o uso atual de Token Plan (janelas de 5h e semanal). " +
|
|
103
|
-
"**Use antes de gerar imagens/vídeos em batch** para evitar estouro de quota. " +
|
|
104
|
-
"Para Pay-as-you-go, mostra saldo disponível. " +
|
|
105
|
-
"Combine com mmx_auth_status pra confirmar método de billing.";
|
|
106
|
-
export const quotaShowInputSchema = z.object({});
|
|
107
|
-
export async function quotaShowHandler(_args) {
|
|
108
|
-
const result = await runMmx(["quota", "show", "--output", "json"]);
|
|
109
|
-
if (result.exitCode !== 0) {
|
|
110
|
-
return handleToolError("consultar quota", result);
|
|
111
|
-
}
|
|
112
|
-
return { content: [{ type: "text", text: result.stdout || "Sem dados de quota." }] };
|
|
113
|
-
}
|
|
114
|
-
// =============================================================================
|
|
115
|
-
// Aggregate export (consumido por tools/index.ts)
|
|
116
|
-
// =============================================================================
|
|
117
|
-
export const authTools = [
|
|
118
|
-
{
|
|
119
|
-
name: "mmx_login",
|
|
120
|
-
description: loginDescription,
|
|
121
|
-
inputSchema: loginInputSchema,
|
|
122
|
-
handler: loginHandler,
|
|
123
|
-
},
|
|
124
|
-
{
|
|
125
|
-
name: "mmx_auth_status",
|
|
126
|
-
description: authStatusDescription,
|
|
127
|
-
inputSchema: authStatusInputSchema,
|
|
128
|
-
handler: authStatusHandler,
|
|
129
|
-
},
|
|
130
|
-
{
|
|
131
|
-
name: "mmx_quota_show",
|
|
132
|
-
description: quotaShowDescription,
|
|
133
|
-
inputSchema: quotaShowInputSchema,
|
|
134
|
-
handler: quotaShowHandler,
|
|
135
|
-
},
|
|
136
|
-
];
|
|
137
|
-
//# sourceMappingURL=auth.js.map
|