Skip to content

Commit 70dd4b2

Browse files
committed
Add temporary skill output
1 parent 98530c7 commit 70dd4b2

8 files changed

Lines changed: 154 additions & 10 deletions

File tree

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,13 @@ This writes:
228228
The generated skill instructs agents to discover tools with focused schema
229229
selectors and call MCP tools through mcpx.
230230

231+
For temporary agent guidance without writing `.agents/skills`, print a one-server
232+
skill to stdout:
233+
234+
```bash
235+
mcpx @skill --show slack
236+
```
237+
231238
## Skill
232239

233240
General mcpx skill definition lives at `skills/mcpx/SKILL.md`.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "mcpx",
3-
"version": "0.9.6",
3+
"version": "0.9.7",
44
"license": "MIT",
55
"bin": {
66
"mcpx": "./src/main.ts"

skills/mcpx/SKILL.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,16 @@ Use the generated project skill as the project-specific router. It should name
159159
servers the user has already made available; do not use this flow to register or
160160
authenticate new MCP servers.
161161

162+
For temporary agent guidance without writing `.agents/skills`, print a
163+
single-server skill to stdout:
164+
165+
```bash
166+
mcpx @skill --show slack
167+
```
168+
169+
Use this when the user asks for a one-off service action during the current
170+
agent session and does not want persistent project skills.
171+
162172
## References
163173

164174
mcpx maintains a background daemon (`mcpxd`) for session reuse and notification

src/router.ts

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,12 @@ const skillInput = s(
7373
v.description("Comma-separated MCP server names, for example posthog,sentry"),
7474
),
7575
),
76+
show: v.optional(
77+
v.pipe(
78+
v.string(),
79+
v.description("Print a temporary skill for one MCP server without writing files"),
80+
),
81+
),
7682
}),
7783
);
7884

@@ -154,9 +160,12 @@ function buildRouter(service: ProjectService): Router {
154160
),
155161
"@skill": c
156162
.meta({
157-
description:
158-
"Generate a project skill that teaches agents which global MCP servers to use.",
159-
examples: ["mcpx @skill", "mcpx @skill --servers posthog,sentry"],
163+
description: "Generate a project skill or print a temporary server skill for agents.",
164+
examples: [
165+
"mcpx @skill",
166+
"mcpx @skill --servers posthog,sentry",
167+
"mcpx @skill --show slack",
168+
],
160169
})
161170
.input(skillInput),
162171
};
@@ -302,7 +311,7 @@ function buildHandlers(service: ProjectService, cwd: string): Record<string, unk
302311
},
303312
};
304313

305-
handlers["@skill"] = async (options: HandlerOptions<{ servers?: string }>) => {
314+
handlers["@skill"] = async (options: HandlerOptions<{ servers?: string; show?: string }>) => {
306315
await runSkillCommand(service, cwd, options.input);
307316
};
308317

src/skill-command.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import { cancel, isCancel, multiselect } from "@clack/prompts";
22

3-
import { readMcpxSkillServers, writeMcpxSkill } from "./skill-template";
3+
import { buildMcpxSkillMarkdown, readMcpxSkillServers, writeMcpxSkill } from "./skill-template";
44
import type { ProjectService } from "./project-service";
55

66
export type SkillCommandInput = {
77
servers?: string;
8+
show?: string;
89
};
910

1011
export async function runSkillCommand(
@@ -19,6 +20,15 @@ export async function runSkillCommand(
1920
);
2021
}
2122

23+
if (input.show !== undefined) {
24+
if (input.servers !== undefined) {
25+
throw new Error("--show cannot be combined with --servers.");
26+
}
27+
const server = normalizeShownServer(input.show, availableServers);
28+
process.stdout.write(buildMcpxSkillMarkdown([server], { projectLocal: false }));
29+
return;
30+
}
31+
2232
const selectedServers =
2333
input.servers === undefined
2434
? await promptForServers(availableServers, await readMcpxSkillServers(cwd))
@@ -67,3 +77,16 @@ function normalizeSelectedServers(value: string, availableServers: string[]): st
6777

6878
return [...new Set(selected)].sort();
6979
}
80+
81+
function normalizeShownServer(value: string, availableServers: string[]): string {
82+
const server = value.trim();
83+
if (server.length === 0 || server.includes(",")) {
84+
throw new Error("Select exactly one MCP server for --show.");
85+
}
86+
87+
const available = new Set(availableServers);
88+
if (!available.has(server)) {
89+
throw new Error(`Unknown MCP server: ${server}`);
90+
}
91+
return server;
92+
}

src/skill-template.ts

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ export type SkillTemplateInput = {
88
servers: string[];
99
};
1010

11+
type SkillMarkdownOptions = {
12+
projectLocal?: boolean;
13+
};
14+
1115
export function mcpxSkillPath(cwd: string): string {
1216
return path.join(cwd, ".agents", "skills", "mcpx", "SKILL.md");
1317
}
@@ -22,10 +26,17 @@ export function buildSchemaSelector(servers: string[]): string {
2226
return `.{${servers.join(",")}}`;
2327
}
2428

25-
export function buildMcpxSkillMarkdown(servers: string[]): string {
29+
export function buildMcpxSkillMarkdown(
30+
servers: string[],
31+
options: SkillMarkdownOptions = {},
32+
): string {
33+
const projectLocal = options.projectLocal ?? true;
2634
const selector = buildSchemaSelector(servers);
2735
const serverList = servers.map((server) => `- ${server}`).join("\n");
28-
const description = `Use project-approved MCP tools through mcpx. Trigger when the user asks to inspect or operate services backed by these MCP servers: ${servers.join(", ")}.`;
36+
const description = projectLocal
37+
? `Use project-approved MCP tools through mcpx. Trigger when the user asks to inspect or operate services backed by these MCP servers: ${servers.join(", ")}.`
38+
: `Use configured MCP tools through mcpx. Trigger when the user asks to inspect or operate services backed by these MCP servers: ${servers.join(", ")}.`;
39+
const scope = projectLocal ? "project-approved" : "configured";
2940

3041
return `---
3142
name: ${JSON.stringify("mcpx")}
@@ -35,7 +46,7 @@ description: ${JSON.stringify(description)}
3546
3647
# MCPX
3748
38-
Use this skill when the task needs one of these MCP servers:
49+
Use this skill when the task needs one of these ${scope} MCP servers:
3950
4051
${serverList}
4152

tests/skill-command.test.ts

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import { mkdtemp, readFile } from "node:fs/promises";
2+
import { tmpdir } from "node:os";
3+
import { join } from "node:path";
4+
5+
import { describe, expect, it } from "bun:test";
6+
7+
import { runSkillCommand } from "../src/skill-command";
8+
import type { ProjectService } from "../src/project-service";
9+
10+
function fixtureService(): ProjectService {
11+
return {
12+
config: {
13+
version: 1,
14+
servers: {
15+
slack: {
16+
transport: "stdio",
17+
command: "slack-mcp",
18+
tools: [],
19+
},
20+
},
21+
},
22+
ensureServerReady: async () => {
23+
throw new Error("not used");
24+
},
25+
reauthenticateServer: async () => {
26+
throw new Error("not used");
27+
},
28+
save: async () => {},
29+
};
30+
}
31+
32+
async function captureStdout(run: () => Promise<void>): Promise<string> {
33+
const originalWrite = process.stdout.write;
34+
let output = "";
35+
process.stdout.write = ((chunk: string | Uint8Array) => {
36+
output += chunk.toString();
37+
return true;
38+
}) as typeof process.stdout.write;
39+
40+
try {
41+
await run();
42+
return output;
43+
} finally {
44+
process.stdout.write = originalWrite;
45+
}
46+
}
47+
48+
describe("mcpx skill command", () => {
49+
it("prints a temporary server skill without writing project files", async () => {
50+
const cwd = await mkdtemp(join(tmpdir(), "mcpx-skill-show-"));
51+
const output = await captureStdout(async () => {
52+
await runSkillCommand(fixtureService(), cwd, { show: "slack" });
53+
});
54+
55+
expect(output).toContain('servers: ["slack"]');
56+
expect(output).toContain("configured MCP servers");
57+
expect(output).toContain('mcpx --schema=".slack"');
58+
await expect(
59+
readFile(join(cwd, ".agents", "skills", "mcpx", "SKILL.md"), "utf8"),
60+
).rejects.toThrow();
61+
});
62+
63+
it("rejects mixing temporary show and project skill generation", async () => {
64+
const cwd = await mkdtemp(join(tmpdir(), "mcpx-skill-show-"));
65+
66+
await expect(
67+
runSkillCommand(fixtureService(), cwd, { servers: "slack", show: "slack" }),
68+
).rejects.toThrow("--show cannot be combined with --servers.");
69+
});
70+
});

tests/skill-template.test.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,12 @@ import { join } from "node:path";
44

55
import { describe, expect, it } from "bun:test";
66

7-
import { buildSchemaSelector, parseMcpxSkillServers, writeMcpxSkill } from "../src/skill-template";
7+
import {
8+
buildMcpxSkillMarkdown,
9+
buildSchemaSelector,
10+
parseMcpxSkillServers,
11+
writeMcpxSkill,
12+
} from "../src/skill-template";
813

914
describe("mcpx skill template", () => {
1015
it("builds argc schema selectors for selected servers", () => {
@@ -31,6 +36,15 @@ describe("mcpx skill template", () => {
3136
expect(content).toContain("mcpx <server> <tool> --input @- <<'JSON'");
3237
});
3338

39+
it("builds temporary mcpx skill markdown without project-local wording", () => {
40+
const content = buildMcpxSkillMarkdown(["slack"], { projectLocal: false });
41+
42+
expect(content).toContain('servers: ["slack"]');
43+
expect(content).toContain("configured MCP servers");
44+
expect(content).not.toContain("project-approved MCP servers");
45+
expect(content).toContain('mcpx --schema=".slack"');
46+
});
47+
3448
it("parses selected servers from existing skill frontmatter", () => {
3549
expect(
3650
parseMcpxSkillServers(`---

0 commit comments

Comments
 (0)