smart-terminal-mcp 1.2.31 → 1.2.32
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/.claude/settings.local.json +4 -1
- package/.mcpregistry_github_token +1 -1
- package/.mcpregistry_registry_token +1 -1
- package/package.json +2 -2
- package/scripts/publish.js +57 -9
- package/server-card.json +7 -2
- package/server.json +2 -2
- package/smithery.json +1 -1
- package/src/command-runner.js +15 -5
- package/src/tools.js +4 -2
- package/test/command-runner.test.js +2 -7
- package/test/tools.test.js +30 -0
|
@@ -3,7 +3,10 @@
|
|
|
3
3
|
"allow": [
|
|
4
4
|
"WebSearch",
|
|
5
5
|
"Bash(dir:*)",
|
|
6
|
-
"Bash(npm test:*)"
|
|
6
|
+
"Bash(npm test:*)",
|
|
7
|
+
"Bash(\"C:\\\\Users\\\\Alessandro\\\\AppData\\\\Local\\\\mcp-publisher\\\\mcp-publisher.exe\" login:*)",
|
|
8
|
+
"Bash(node -e \"console.log\\(require\\('./package.json'\\).version\\)\")",
|
|
9
|
+
"Bash(npm view *)"
|
|
7
10
|
]
|
|
8
11
|
}
|
|
9
12
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
ghu_oOdnT8lgxyaN964Yo3rMrjHyBj8zcd2uGDnB
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"token":"eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.
|
|
1
|
+
{"token":"eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJtY3AtcmVnaXN0cnkiLCJleHAiOjE3NzUyMDMwODcsIm5iZiI6MTc3NTIwMjc4NywiaWF0IjoxNzc1MjAyNzg3LCJhdXRoX21ldGhvZCI6ImdpdGh1Yi1hdCIsImF1dGhfbWV0aG9kX3N1YiI6InB1bmdnZ2kiLCJwZXJtaXNzaW9ucyI6W3siYWN0aW9uIjoicHVibGlzaCIsInJlc291cmNlIjoiaW8uZ2l0aHViLnB1bmdnZ2kvKiJ9XX0.m5_YrGmyIMZ7vVMZBU5G31PBgEfVfWI4WmGZvkNhmk4wSL_cxEF-M2ARhgoymUInngh6qgdql2FLX_SJszlqAw","expires_at":1775203087}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "smart-terminal-mcp",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.32",
|
|
4
4
|
"description": "MCP PTY server providing AI agents with real interactive terminal access",
|
|
5
5
|
"mcpName": "io.github.pungggi/smart-terminal",
|
|
6
6
|
"repository": {
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"scripts": {
|
|
16
16
|
"start": "node src/index.js",
|
|
17
17
|
"test": "node --test",
|
|
18
|
-
"stable": "npm dist-tag add smart-terminal-mcp@1.2.
|
|
18
|
+
"stable": "npm dist-tag add smart-terminal-mcp@1.2.30 stable",
|
|
19
19
|
"release": "node scripts/publish.js"
|
|
20
20
|
},
|
|
21
21
|
"keywords": [
|
package/scripts/publish.js
CHANGED
|
@@ -48,6 +48,16 @@ function runCommand(command, errorMessage, extraEnv = {}) {
|
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
function parseFlags(args) {
|
|
52
|
+
const flags = new Set();
|
|
53
|
+
const positional = [];
|
|
54
|
+
for (const arg of args) {
|
|
55
|
+
if (arg.startsWith('--')) flags.add(arg);
|
|
56
|
+
else positional.push(arg);
|
|
57
|
+
}
|
|
58
|
+
return { flags, positional };
|
|
59
|
+
}
|
|
60
|
+
|
|
51
61
|
function createServerCardReaderInit() {
|
|
52
62
|
return parseExpression(
|
|
53
63
|
`JSON.parse(readFileSync(new URL('${SERVER_CARD_FILE_URL}', import.meta.url), 'utf8'))`,
|
|
@@ -510,10 +520,16 @@ function updateVersionFiles(nextVersion) {
|
|
|
510
520
|
}
|
|
511
521
|
|
|
512
522
|
export async function main(args = process.argv.slice(2)) {
|
|
523
|
+
const { flags, positional } = parseFlags(args);
|
|
524
|
+
const skipNpm = flags.has('--skip-npm');
|
|
525
|
+
const skipRegistry = flags.has('--skip-registry');
|
|
526
|
+
const skipSmithery = flags.has('--skip-smithery');
|
|
527
|
+
|
|
513
528
|
let fileSnapshots = null;
|
|
529
|
+
let npmPublished = false;
|
|
514
530
|
|
|
515
531
|
try {
|
|
516
|
-
const versionArg =
|
|
532
|
+
const versionArg = positional[0] || 'patch';
|
|
517
533
|
const pkg = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
|
|
518
534
|
const currentVersion = pkg.version;
|
|
519
535
|
const nextVersion = bumpVersion(currentVersion, versionArg);
|
|
@@ -529,23 +545,55 @@ export async function main(args = process.argv.slice(2)) {
|
|
|
529
545
|
updateServerCard(tools, nextVersion);
|
|
530
546
|
|
|
531
547
|
// ── Step 3: npm ──
|
|
532
|
-
|
|
533
|
-
|
|
548
|
+
if (skipNpm) {
|
|
549
|
+
console.log('\n📦 Phase 1: Publishing to npm... SKIPPED (--skip-npm)');
|
|
550
|
+
} else {
|
|
551
|
+
console.log('\n📦 Phase 1: Publishing to npm...');
|
|
552
|
+
runCommand('npm publish --access public', 'npm publishing failed.');
|
|
553
|
+
npmPublished = true;
|
|
554
|
+
}
|
|
534
555
|
|
|
535
556
|
// ── Step 4: Official MCP Registry ──
|
|
536
|
-
|
|
537
|
-
|
|
557
|
+
if (skipRegistry) {
|
|
558
|
+
console.log('\n🌍 Phase 2: Publishing to Official MCP Registry... SKIPPED (--skip-registry)');
|
|
559
|
+
} else {
|
|
560
|
+
console.log('\n🌍 Phase 2: Publishing to Official MCP Registry...');
|
|
561
|
+
try {
|
|
562
|
+
runCommand(`${getMcpPublisher()} publish`, 'Official MCP Registry publishing failed.');
|
|
563
|
+
} catch (error) {
|
|
564
|
+
const causeStderr = error.cause?.stderr?.toString?.() ?? '';
|
|
565
|
+
const causeMessage = error.message ?? '';
|
|
566
|
+
if (causeStderr.includes('401') || causeStderr.includes('Unauthorized') || causeStderr.includes('expired')
|
|
567
|
+
|| causeMessage.includes('401') || causeMessage.includes('Unauthorized') || causeMessage.includes('expired')) {
|
|
568
|
+
const pub = getMcpPublisher();
|
|
569
|
+
console.error(`\n💡 Auth token expired. Run: ${pub} login github`);
|
|
570
|
+
console.error(` Then retry with: npm run release -- ${versionArg} --skip-npm`);
|
|
571
|
+
}
|
|
572
|
+
throw error;
|
|
573
|
+
}
|
|
574
|
+
}
|
|
538
575
|
|
|
539
576
|
// ── Step 5: Smithery.ai (HTTP server + tunnel + publish) ──
|
|
540
|
-
|
|
577
|
+
if (skipSmithery) {
|
|
578
|
+
console.log('\n💎 Phase 3: Publishing to Smithery.ai... SKIPPED (--skip-smithery)');
|
|
579
|
+
} else {
|
|
580
|
+
await publishToSmithery();
|
|
581
|
+
}
|
|
541
582
|
|
|
542
|
-
|
|
583
|
+
const skipped = [skipNpm && 'npm', skipRegistry && 'registry', skipSmithery && 'smithery'].filter(Boolean);
|
|
584
|
+
const suffix = skipped.length ? ` (skipped: ${skipped.join(', ')})` : ' to all registries';
|
|
585
|
+
console.log(`\n✅ SUCCESSFULLY PUBLISHED version ${nextVersion}${suffix}!`);
|
|
543
586
|
} catch (error) {
|
|
544
587
|
console.error('\n❌ Publish failed.');
|
|
545
588
|
console.error(error);
|
|
546
589
|
if (fileSnapshots) {
|
|
547
|
-
|
|
548
|
-
|
|
590
|
+
if (npmPublished) {
|
|
591
|
+
// npm already has this version — don't revert local files or we'll get version drift.
|
|
592
|
+
console.error('\n⚠️ npm was already published. Local version files kept at the new version.');
|
|
593
|
+
console.error(' Fix the issue and re-run with --skip-npm to complete remaining phases.');
|
|
594
|
+
} else {
|
|
595
|
+
revertVersionChanges(fileSnapshots);
|
|
596
|
+
}
|
|
549
597
|
}
|
|
550
598
|
throw error;
|
|
551
599
|
}
|
package/server-card.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"serverInfo": {
|
|
3
3
|
"name": "smart-terminal-mcp",
|
|
4
|
-
"version": "1.2.
|
|
4
|
+
"version": "1.2.32"
|
|
5
5
|
},
|
|
6
6
|
"tools": [
|
|
7
7
|
{
|
|
@@ -77,7 +77,7 @@
|
|
|
77
77
|
},
|
|
78
78
|
{
|
|
79
79
|
"name": "terminal_run",
|
|
80
|
-
"description": "Run a binary directly
|
|
80
|
+
"description": "Run a binary directly. shell=true for built-ins/pipes/redirects.",
|
|
81
81
|
"inputSchema": {
|
|
82
82
|
"type": "object",
|
|
83
83
|
"properties": {
|
|
@@ -138,6 +138,11 @@
|
|
|
138
138
|
"successFilePattern": {
|
|
139
139
|
"type": "string",
|
|
140
140
|
"description": "Regex"
|
|
141
|
+
},
|
|
142
|
+
"shell": {
|
|
143
|
+
"type": "boolean",
|
|
144
|
+
"default": false,
|
|
145
|
+
"description": "Run via system shell"
|
|
141
146
|
}
|
|
142
147
|
},
|
|
143
148
|
"required": [
|
package/server.json
CHANGED
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
"url": "https://github.com/pungggi/smart-terminal-mcp",
|
|
7
7
|
"source": "github"
|
|
8
8
|
},
|
|
9
|
-
"version": "1.2.
|
|
9
|
+
"version": "1.2.32",
|
|
10
10
|
"packages": [
|
|
11
11
|
{
|
|
12
12
|
"registryType": "npm",
|
|
13
13
|
"identifier": "smart-terminal-mcp",
|
|
14
|
-
"version": "1.2.
|
|
14
|
+
"version": "1.2.32",
|
|
15
15
|
"transport": {
|
|
16
16
|
"type": "stdio"
|
|
17
17
|
}
|
package/smithery.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pungggi/smart-terminal",
|
|
3
3
|
"description": "MCP PTY server providing AI agents with real interactive terminal access",
|
|
4
|
-
"version": "1.2.
|
|
4
|
+
"version": "1.2.32",
|
|
5
5
|
"repository": "https://github.com/pungggi/smart-terminal-mcp",
|
|
6
6
|
"tags": [
|
|
7
7
|
"terminal",
|
package/src/command-runner.js
CHANGED
|
@@ -26,11 +26,12 @@ export async function runCommand({
|
|
|
26
26
|
successExitCode = 0,
|
|
27
27
|
successFile,
|
|
28
28
|
successFilePattern,
|
|
29
|
+
shell = false,
|
|
29
30
|
}) {
|
|
30
31
|
assertSuccessChecksAreValid({ successFile, successFilePattern });
|
|
31
32
|
const resolvedCwd = resolvePath(cwd ?? process.cwd());
|
|
32
33
|
const startedAt = Date.now();
|
|
33
|
-
const spawnPlan = buildSpawnPlan({ cmd, args, cwd: resolvedCwd });
|
|
34
|
+
const spawnPlan = buildSpawnPlan({ cmd, args, cwd: resolvedCwd, useShell: shell });
|
|
34
35
|
|
|
35
36
|
return new Promise((resolve, reject) => {
|
|
36
37
|
const stdoutChunks = [];
|
|
@@ -42,7 +43,7 @@ export async function runCommand({
|
|
|
42
43
|
|
|
43
44
|
const child = spawn(spawnPlan.command, spawnPlan.args, {
|
|
44
45
|
cwd: resolvedCwd,
|
|
45
|
-
shell: false,
|
|
46
|
+
shell: spawnPlan.shell ?? false,
|
|
46
47
|
windowsHide: true,
|
|
47
48
|
windowsVerbatimArguments: spawnPlan.windowsVerbatimArguments,
|
|
48
49
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
@@ -222,7 +223,16 @@ function formatFileCheckError(error) {
|
|
|
222
223
|
return error?.message ?? String(error);
|
|
223
224
|
}
|
|
224
225
|
|
|
225
|
-
function buildSpawnPlan({ cmd, args, cwd }) {
|
|
226
|
+
function buildSpawnPlan({ cmd, args, cwd, useShell = false }) {
|
|
227
|
+
if (useShell) {
|
|
228
|
+
return {
|
|
229
|
+
command: cmd,
|
|
230
|
+
args,
|
|
231
|
+
shell: true,
|
|
232
|
+
windowsVerbatimArguments: false,
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
|
|
226
236
|
if (process.platform !== 'win32') {
|
|
227
237
|
return {
|
|
228
238
|
command: cmd,
|
|
@@ -372,11 +382,11 @@ function quoteWindowsBatchArgument(value) {
|
|
|
372
382
|
|
|
373
383
|
function formatStartError({ cmd, err }) {
|
|
374
384
|
const baseMessage = `Failed to start command "${cmd}": ${err.message}`;
|
|
375
|
-
if (
|
|
385
|
+
if (err?.code !== 'ENOENT' || looksLikePath(cmd)) {
|
|
376
386
|
return baseMessage;
|
|
377
387
|
}
|
|
378
388
|
|
|
379
|
-
return `${baseMessage}.
|
|
389
|
+
return `${baseMessage}. Verify it is installed and on PATH for the server process. For shell built-ins, pipes, or redirections, pass shell:true or use terminal_start + terminal_exec.`;
|
|
380
390
|
}
|
|
381
391
|
|
|
382
392
|
export function getStructuredParserHint({ cmd, args, ok, parseRequested, parsed, stdout }) {
|
package/src/tools.js
CHANGED
|
@@ -152,7 +152,7 @@ export function registerTools(server, manager) {
|
|
|
152
152
|
// --- terminal_run ---
|
|
153
153
|
tool(
|
|
154
154
|
'terminal_run',
|
|
155
|
-
'Run a binary directly
|
|
155
|
+
'Run a binary directly. shell=true for built-ins/pipes/redirects.',
|
|
156
156
|
{
|
|
157
157
|
cmd: z.string(),
|
|
158
158
|
args: z.array(z.string()).default([]),
|
|
@@ -165,8 +165,9 @@ export function registerTools(server, manager) {
|
|
|
165
165
|
successExitCode: z.number().int().nullable().default(0).describe('null=any'),
|
|
166
166
|
successFile: z.string().optional(),
|
|
167
167
|
successFilePattern: z.string().optional().describe('Regex'),
|
|
168
|
+
shell: z.boolean().default(false).describe('Run via system shell'),
|
|
168
169
|
},
|
|
169
|
-
async ({ cmd, args, cwd, timeout, maxOutputBytes, parse, parseOnly, summary, successExitCode, successFile, successFilePattern }) => {
|
|
170
|
+
async ({ cmd, args, cwd, timeout, maxOutputBytes, parse, parseOnly, summary, successExitCode, successFile, successFilePattern, shell }) => {
|
|
170
171
|
const result = await runCommand({
|
|
171
172
|
cmd,
|
|
172
173
|
args,
|
|
@@ -179,6 +180,7 @@ export function registerTools(server, manager) {
|
|
|
179
180
|
successExitCode,
|
|
180
181
|
successFile,
|
|
181
182
|
successFilePattern,
|
|
183
|
+
shell,
|
|
182
184
|
});
|
|
183
185
|
return jsonContent(result);
|
|
184
186
|
}
|
|
@@ -288,18 +288,13 @@ test('runCommand handles explicit command paths with spaces', async () => {
|
|
|
288
288
|
}
|
|
289
289
|
});
|
|
290
290
|
|
|
291
|
-
test('runCommand adds a helpful ENOENT hint
|
|
292
|
-
if (process.platform !== 'win32') {
|
|
293
|
-
t.skip('Windows-only behavior');
|
|
294
|
-
return;
|
|
295
|
-
}
|
|
296
|
-
|
|
291
|
+
test('runCommand adds a helpful ENOENT hint for PATH commands', async () => {
|
|
297
292
|
await assert.rejects(
|
|
298
293
|
runCommand({
|
|
299
294
|
cmd: '__smart_terminal_missing_command__',
|
|
300
295
|
parse: false,
|
|
301
296
|
}),
|
|
302
|
-
/
|
|
297
|
+
/pass shell:true or use terminal_start \+ terminal_exec/
|
|
303
298
|
);
|
|
304
299
|
});
|
|
305
300
|
|
package/test/tools.test.js
CHANGED
|
@@ -288,6 +288,36 @@ test('terminal_run can re-evaluate success from a file pattern', async () => {
|
|
|
288
288
|
}
|
|
289
289
|
});
|
|
290
290
|
|
|
291
|
+
test('terminal_run shell=true executes commands via the system shell', async () => {
|
|
292
|
+
const server = createFakeServer();
|
|
293
|
+
registerTools(server, {});
|
|
294
|
+
|
|
295
|
+
const result = await server.tools.get('terminal_run').handler({
|
|
296
|
+
cmd: 'echo shell-ok',
|
|
297
|
+
args: [],
|
|
298
|
+
shell: true,
|
|
299
|
+
parse: false,
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
const payload = JSON.parse(result.content[0].text);
|
|
303
|
+
assert.equal(payload.ok, true);
|
|
304
|
+
assert.match(payload.stdout.raw, /shell-ok/);
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
test('terminal_run ENOENT error hints at shell=true and terminal_exec fallback', async () => {
|
|
308
|
+
const server = createFakeServer();
|
|
309
|
+
registerTools(server, {});
|
|
310
|
+
|
|
311
|
+
await assert.rejects(
|
|
312
|
+
() => server.tools.get('terminal_run').handler({
|
|
313
|
+
cmd: 'smart-terminal-missing-binary-xyz',
|
|
314
|
+
args: [],
|
|
315
|
+
parse: false,
|
|
316
|
+
}),
|
|
317
|
+
/shell:true or use terminal_start \+ terminal_exec/
|
|
318
|
+
);
|
|
319
|
+
});
|
|
320
|
+
|
|
291
321
|
test('terminal_read rejects idleTimeout values that are not less than timeout', async () => {
|
|
292
322
|
const server = createFakeServer();
|
|
293
323
|
let getCalls = 0;
|