toolcapsule 0.1.0-alpha.0 → 0.1.0-alpha.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/README.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # ToolCapsule
2
2
 
3
+ [![npm version](https://img.shields.io/npm/v/toolcapsule?style=flat-square)](https://www.npmjs.com/package/toolcapsule)
4
+ [![CI](https://img.shields.io/github/actions/workflow/status/RainSunMe/toolcapsule/ci.yml?branch=main&style=flat-square)](https://github.com/RainSunMe/toolcapsule/actions/workflows/ci.yml)
5
+ [![License: MIT](https://img.shields.io/badge/license-MIT-black?style=flat-square)](LICENSE)
6
+ [![GitHub Repo stars](https://img.shields.io/github/stars/RainSunMe/toolcapsule?style=flat-square)](https://github.com/RainSunMe/toolcapsule)
7
+
3
8
  > Heavy MCP tools don't belong in your prompt. Put them in a ToolCapsule.
4
9
 
5
10
  **ToolCapsule** turns heavy MCP servers into lightweight, lazy-loaded, file-first **Agent Skills** with patch-and-retry workflows.
@@ -44,7 +49,7 @@ npm i -g toolcapsule
44
49
 
45
50
  toolcapsule init feishu --url https://mcp.example.com/mcp/xxx
46
51
  toolcapsule tools feishu --brief
47
- toolcapsule describe feishu create-doc --brief
52
+ toolcapsule schema feishu create-doc
48
53
  toolcapsule call feishu create-doc @args.json --save-run
49
54
  toolcapsule retry runs/2026-06-06T10-00-00-000Z
50
55
  ```
@@ -112,10 +117,11 @@ toolcapsule init <name> --url <remote-mcp-url>
112
117
  toolcapsule init <name> --command <stdio-command> --arg <arg>
113
118
  toolcapsule tools <profile> --brief
114
119
  toolcapsule describe <profile> <tool> --brief
120
+ toolcapsule schema <profile> <tool>
115
121
  toolcapsule call <profile> <tool> @args.json --save-run
116
122
  toolcapsule retry <run-dir>
117
123
  toolcapsule summarize <profile>
118
- toolcapsule benchmark <profile>
124
+ toolcapsule benchmark <profile> --markdown --out toolcapsule-report.md
119
125
  ```
120
126
 
121
127
  `tcap` is an equivalent short alias for `toolcapsule`.
@@ -142,6 +148,9 @@ Early alpha. APIs may change before v1.0.
142
148
  - [Architecture](docs/architecture.md)
143
149
  - [Patch and retry](docs/patch-and-retry.md)
144
150
  - [Benchmark methodology](docs/benchmark-methodology.md)
151
+ - [Releasing](docs/releasing.md)
152
+ - [Launch notes](docs/launch.md)
153
+ - [Next steps](docs/next-steps.md)
145
154
  - [Release checklist](docs/release-checklist.md)
146
155
  - [Roadmap](ROADMAP.md)
147
156
 
package/dist/cli.js CHANGED
@@ -288,6 +288,9 @@ async function loadRun(runDir) {
288
288
  return JSON.parse(await readFile2(join3(runDir, "run.json"), "utf8"));
289
289
  }
290
290
 
291
+ // src/cli.ts
292
+ import { writeFile as writeFile4 } from "fs/promises";
293
+
291
294
  // src/utils/tokens.ts
292
295
  function roughTokens(value) {
293
296
  const text = typeof value === "string" ? value : JSON.stringify(value);
@@ -299,6 +302,15 @@ function percentReduction(before, after) {
299
302
 
300
303
  // src/cli.ts
301
304
  var cli = cac("toolcapsule");
305
+ async function readPackageVersion() {
306
+ try {
307
+ const pkg = JSON.parse(await readFile3(new URL("../package.json", import.meta.url), "utf8"));
308
+ return pkg.version || "0.0.0";
309
+ } catch {
310
+ return "0.0.0";
311
+ }
312
+ }
313
+ var packageVersion = await readPackageVersion();
302
314
  async function withClient(profile, fn) {
303
315
  const client = new McpClient(profile);
304
316
  try {
@@ -332,6 +344,13 @@ cli.command("describe <profile> <tool>", "Describe one MCP tool").option("--brie
332
344
  if (!tool) throw new Error(`Tool not found: ${toolName}`);
333
345
  console.log(JSON.stringify(options.brief ? summarizeTool(tool) : tool, null, 2));
334
346
  });
347
+ cli.command("schema <profile> <tool>", "Print a compact schema for one MCP tool").action(async (profileName, toolName) => {
348
+ const profile = await loadProfile(profileName);
349
+ const result = await withClient(profile, (client) => client.listTools());
350
+ const tool = result.tools.find((item) => item.name === toolName);
351
+ if (!tool) throw new Error(`Tool not found: ${toolName}`);
352
+ console.log(JSON.stringify(summarizeTool(tool), null, 2));
353
+ });
335
354
  cli.command("call <profile> <tool> <args>", "Call an MCP tool with JSON args or @args.json").option("--save-run", "Save request, response, and command under runs/").action(async (profileName, toolName, argsRaw, options) => {
336
355
  const argsPath = readArgsPath(argsRaw);
337
356
  const toolArgs = argsRaw.startsWith("@") ? await readJson(argsPath) : JSON.parse(argsRaw);
@@ -386,31 +405,38 @@ cli.command("summarize <profile>", "Summarize all tools into compact JSON").acti
386
405
  const result = await withClient(profile, (client) => client.listTools());
387
406
  console.log(JSON.stringify(summarizeTools(result.tools), null, 2));
388
407
  });
389
- cli.command("benchmark <profile>", "Estimate schema savings for a profile").action(async (profileName) => {
408
+ cli.command("benchmark <profile>", "Estimate schema savings for a profile").option("--markdown", "Print a Markdown report").option("--out <file>", "Write report to a file").action(async (profileName, options) => {
390
409
  const profile = await loadProfile(profileName);
391
410
  const result = await withClient(profile, (client) => client.listTools());
392
411
  const nativeTokens = roughTokens(result);
393
412
  const brief = summarizeTools(result.tools);
394
413
  const briefTokens = roughTokens(brief);
395
- console.log(
396
- JSON.stringify(
397
- {
398
- profile: profileName,
399
- tools: result.tools.length,
400
- nativeToolsRoughTokens: nativeTokens,
401
- summarizedToolsRoughTokens: briefTokens,
402
- estimatedReductionPct: Number(percentReduction(nativeTokens, briefTokens).toFixed(2))
403
- },
404
- null,
405
- 2
406
- )
407
- );
414
+ const summary = {
415
+ profile: profileName,
416
+ tools: result.tools.length,
417
+ nativeToolsRoughTokens: nativeTokens,
418
+ summarizedToolsRoughTokens: briefTokens,
419
+ estimatedReductionPct: Number(percentReduction(nativeTokens, briefTokens).toFixed(2))
420
+ };
421
+ const output = options.markdown ? `# ToolCapsule benchmark: ${profileName}
422
+
423
+ | Metric | Value |
424
+ |---|---:|
425
+ | MCP tools | ${summary.tools} |
426
+ | Native MCP schema rough tokens | ${summary.nativeToolsRoughTokens} |
427
+ | ToolCapsule summary rough tokens | ${summary.summarizedToolsRoughTokens} |
428
+ | Estimated reduction | ${summary.estimatedReductionPct}% |
429
+
430
+ > Rough tokens are estimated from serialized schema length. Use this report to compare schema footprint before and after capsule summaries.
431
+ ` : JSON.stringify(summary, null, 2);
432
+ if (options.out) await writeFile4(options.out, output);
433
+ console.log(output);
408
434
  });
409
435
  cli.command("render-readme", "Print website hero copy snippets").action(async () => {
410
436
  console.log(await readFile3(new URL("../docs/hero-copy.md", import.meta.url), "utf8"));
411
437
  });
412
438
  cli.help();
413
- cli.version("0.1.0-alpha.0");
439
+ cli.version(packageVersion);
414
440
  try {
415
441
  cli.parse();
416
442
  } catch (error) {
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/cli.ts","../src/mcp/client.ts","../src/profile.ts","../src/utils/fs.ts","../src/schema/brief.ts","../src/schema/summarize.ts","../src/skill/generator.ts","../src/runs/recorder.ts","../src/utils/tokens.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { readFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { cac } from \"cac\";\nimport pc from \"picocolors\";\nimport type { ProfileConfig } from \"./types.js\";\nimport { McpClient } from \"./mcp/client.js\";\nimport { loadProfile } from \"./profile.js\";\nimport { briefTools } from \"./schema/brief.js\";\nimport { summarizeTool, summarizeTools } from \"./schema/summarize.js\";\nimport { generateSkill, writeProfile } from \"./skill/generator.js\";\nimport { createRunId, loadRun, saveRun } from \"./runs/recorder.js\";\nimport { readJson } from \"./utils/fs.js\";\nimport { percentReduction, roughTokens } from \"./utils/tokens.js\";\n\nconst cli = cac(\"toolcapsule\");\n\nasync function withClient<T>(profile: ProfileConfig, fn: (client: McpClient) => Promise<T>): Promise<T> {\n const client = new McpClient(profile);\n try {\n await client.init();\n return await fn(client);\n } finally {\n await client.close();\n }\n}\n\nfunction readArgsPath(raw: string): string {\n return raw.startsWith(\"@\") ? raw.slice(1) : raw;\n}\n\ncli\n .command(\"init <name>\", \"Create a profile and generated Agent Skill for an MCP server\")\n .option(\"--url <url>\", \"Remote MCP URL\")\n .option(\"--command <command>\", \"stdio MCP command\")\n .option(\"--arg <arg>\", \"stdio MCP argument, repeatable\", { type: [String] })\n .option(\"--output <dir>\", \"Skill output directory\")\n .action(async (name: string, options: { url?: string; command?: string; arg?: string[]; output?: string }) => {\n if (!options.url && !options.command) throw new Error(\"Provide --url for remote MCP or --command for stdio MCP\");\n const profile: ProfileConfig = options.url\n ? { name, transport: { type: \"remote\", url: options.url } }\n : { name, transport: { type: \"stdio\", command: options.command!, args: options.arg ?? [] } };\n await writeProfile(join(\".toolcapsule\", \"profiles\", `${name}.json`), profile);\n const out = await generateSkill(profile, options.output ? { outputDir: options.output } : {});\n console.log(pc.green(`Created profile and skill at ${out}`));\n });\n\ncli\n .command(\"tools <profile>\", \"List MCP tools\")\n .option(\"--brief\", \"Print compact tool summaries\")\n .option(\"--names\", \"Print tool names only\")\n .option(\"--json\", \"Print raw JSON\")\n .action(async (profileName: string, options: { brief?: boolean; names?: boolean; json?: boolean }) => {\n const profile = await loadProfile(profileName);\n const result = await withClient(profile, (client) => client.listTools());\n if (options.json) console.log(JSON.stringify(result, null, 2));\n else if (options.names) console.log(result.tools.map((tool) => tool.name).join(\"\\n\"));\n else console.log(briefTools(result.tools));\n });\n\ncli\n .command(\"describe <profile> <tool>\", \"Describe one MCP tool\")\n .option(\"--brief\", \"Print summarized schema instead of raw schema\")\n .action(async (profileName: string, toolName: string, options: { brief?: boolean }) => {\n const profile = await loadProfile(profileName);\n const result = await withClient(profile, (client) => client.listTools());\n const tool = result.tools.find((item) => item.name === toolName);\n if (!tool) throw new Error(`Tool not found: ${toolName}`);\n console.log(JSON.stringify(options.brief ? summarizeTool(tool) : tool, null, 2));\n });\n\ncli\n .command(\"call <profile> <tool> <args>\", \"Call an MCP tool with JSON args or @args.json\")\n .option(\"--save-run\", \"Save request, response, and command under runs/\")\n .action(async (profileName: string, toolName: string, argsRaw: string, options: { saveRun?: boolean }) => {\n const argsPath = readArgsPath(argsRaw);\n const toolArgs = argsRaw.startsWith(\"@\") ? await readJson(argsPath) : JSON.parse(argsRaw);\n const profile = await loadProfile(profileName);\n const request = { name: toolName, arguments: toolArgs };\n const command = `toolcapsule call ${profileName} ${toolName} ${argsRaw}`;\n const runId = createRunId();\n try {\n const response = await withClient(profile, (client) => client.callTool(toolName, toolArgs));\n console.log(JSON.stringify(response, null, 2));\n if (options.saveRun) {\n const dir = await saveRun(\"runs\", {\n id: runId,\n createdAt: new Date().toISOString(),\n profile: profileName,\n tool: toolName,\n argsFile: argsPath,\n status: \"success\",\n command,\n request,\n response,\n });\n console.error(pc.green(`Saved run: ${dir}`));\n }\n } catch (error) {\n if (options.saveRun) {\n const dir = await saveRun(\"runs\", {\n id: runId,\n createdAt: new Date().toISOString(),\n profile: profileName,\n tool: toolName,\n argsFile: argsPath,\n status: \"error\",\n command,\n request,\n error: error instanceof Error ? error.message : String(error),\n });\n console.error(pc.yellow(`Saved failed run: ${dir}`));\n }\n throw error;\n }\n });\n\ncli.command(\"retry <runDir>\", \"Retry a saved run, re-reading the args file\").action(async (runDir: string) => {\n const run = await loadRun(runDir);\n const toolArgs = await readJson(run.argsFile);\n const profile = await loadProfile(run.profile);\n const response = await withClient(profile, (client) => client.callTool(run.tool, toolArgs));\n console.log(JSON.stringify(response, null, 2));\n});\n\ncli.command(\"summarize <profile>\", \"Summarize all tools into compact JSON\").action(async (profileName: string) => {\n const profile = await loadProfile(profileName);\n const result = await withClient(profile, (client) => client.listTools());\n console.log(JSON.stringify(summarizeTools(result.tools), null, 2));\n});\n\ncli.command(\"benchmark <profile>\", \"Estimate schema savings for a profile\").action(async (profileName: string) => {\n const profile = await loadProfile(profileName);\n const result = await withClient(profile, (client) => client.listTools());\n const nativeTokens = roughTokens(result);\n const brief = summarizeTools(result.tools);\n const briefTokens = roughTokens(brief);\n console.log(\n JSON.stringify(\n {\n profile: profileName,\n tools: result.tools.length,\n nativeToolsRoughTokens: nativeTokens,\n summarizedToolsRoughTokens: briefTokens,\n estimatedReductionPct: Number(percentReduction(nativeTokens, briefTokens).toFixed(2)),\n },\n null,\n 2,\n ),\n );\n});\n\ncli.command(\"render-readme\", \"Print website hero copy snippets\").action(async () => {\n console.log(await readFile(new URL(\"../docs/hero-copy.md\", import.meta.url), \"utf8\"));\n});\n\ncli.help();\ncli.version(\"0.1.0-alpha.0\");\n\ntry {\n cli.parse();\n} catch (error) {\n console.error(pc.red(error instanceof Error ? error.message : String(error)));\n process.exit(1);\n}\n","import { spawn, type ChildProcessWithoutNullStreams } from \"node:child_process\";\nimport { once } from \"node:events\";\nimport type { JsonRpcMessage, ProfileConfig, ToolsListResult } from \"../types.js\";\n\nexport type McpClientOptions = {\n timeoutMs?: number;\n};\n\nexport class McpClient {\n private child: ChildProcessWithoutNullStreams;\n private nextId = 1;\n private buffer = \"\";\n private pending = new Map<\n number,\n { resolve: (value: JsonRpcMessage) => void; reject: (error: Error) => void }\n >();\n private timeoutMs: number;\n\n constructor(profile: ProfileConfig, opts: McpClientOptions = {}) {\n this.timeoutMs = opts.timeoutMs ?? Number(process.env.TOOLCAPSULE_TIMEOUT_MS || \"45000\");\n if (profile.transport.type === \"remote\") {\n this.child = spawn(\"npx\", [\"-y\", \"mcp-remote\", profile.transport.url], {\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n });\n } else {\n this.child = spawn(profile.transport.command, profile.transport.args ?? [], {\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n });\n }\n this.child.stdout.setEncoding(\"utf8\");\n this.child.stdout.on(\"data\", (chunk: string) => this.onStdout(chunk));\n this.child.stderr.on(\"data\", (chunk: Buffer) => process.stderr.write(chunk));\n this.child.on(\"exit\", (code, signal) => {\n const error = new Error(`MCP process exited early (code=${code}, signal=${signal})`);\n for (const waiter of this.pending.values()) waiter.reject(error);\n this.pending.clear();\n });\n }\n\n private onStdout(chunk: string): void {\n this.buffer += chunk;\n let newlineIndex: number;\n while ((newlineIndex = this.buffer.indexOf(\"\\n\")) >= 0) {\n const line = this.buffer.slice(0, newlineIndex).trim();\n this.buffer = this.buffer.slice(newlineIndex + 1);\n if (!line) continue;\n let message: JsonRpcMessage;\n try {\n message = JSON.parse(line) as JsonRpcMessage;\n } catch {\n process.stderr.write(`${line}\\n`);\n continue;\n }\n if (typeof message.id === \"number\") {\n const waiter = this.pending.get(message.id);\n if (waiter) {\n this.pending.delete(message.id);\n waiter.resolve(message);\n }\n }\n }\n }\n\n async init(): Promise<void> {\n await this.request(\"initialize\", {\n protocolVersion: \"2025-03-26\",\n capabilities: {},\n clientInfo: { name: \"toolcapsule\", version: \"0.1.0\" },\n });\n this.notify(\"notifications/initialized\", {});\n }\n\n async request(method: string, params?: unknown): Promise<unknown> {\n const id = this.nextId++;\n const responsePromise = new Promise<JsonRpcMessage>((resolve, reject) => {\n this.pending.set(id, { resolve, reject });\n });\n this.child.stdin.write(`${JSON.stringify({ jsonrpc: \"2.0\", id, method, params })}\\n`);\n const response = await this.withTimeout(responsePromise, method);\n if (response.error) throw new Error(`${method} failed: ${JSON.stringify(response.error, null, 2)}`);\n return response.result;\n }\n\n notify(method: string, params?: unknown): void {\n this.child.stdin.write(`${JSON.stringify({ jsonrpc: \"2.0\", method, params })}\\n`);\n }\n\n async listTools(): Promise<ToolsListResult> {\n return (await this.request(\"tools/list\", {})) as ToolsListResult;\n }\n\n async callTool(name: string, args: unknown): Promise<unknown> {\n return await this.request(\"tools/call\", { name, arguments: args });\n }\n\n async close(): Promise<void> {\n if (!this.child.killed) this.child.kill();\n if (this.child.exitCode === null) await Promise.race([once(this.child, \"exit\"), new Promise((resolve) => setTimeout(resolve, 500))]);\n }\n\n private async withTimeout<T>(promise: Promise<T>, label: string): Promise<T> {\n let timer: NodeJS.Timeout | undefined;\n const timeout = new Promise<never>((_, reject) => {\n timer = setTimeout(() => reject(new Error(`${label} timed out after ${this.timeoutMs}ms`)), this.timeoutMs);\n });\n try {\n return await Promise.race([promise, timeout]);\n } finally {\n if (timer) clearTimeout(timer);\n }\n }\n}\n","import { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { z } from \"zod\";\nimport type { ProfileConfig } from \"./types.js\";\nimport { readJson } from \"./utils/fs.js\";\n\nconst profileSchema = z.object({\n name: z.string().min(1),\n transport: z.union([\n z.object({ type: z.literal(\"remote\"), url: z.string().url() }),\n z.object({ type: z.literal(\"stdio\"), command: z.string().min(1), args: z.array(z.string()).optional() }),\n ]),\n skill: z\n .object({\n name: z.string().optional(),\n description: z.string().optional(),\n })\n .optional(),\n shortcuts: z\n .record(\n z.object({\n tool: z.string(),\n description: z.string().optional(),\n args: z.record(z.enum([\"string\", \"file\", \"json\", \"boolean\", \"number\"])).optional(),\n }),\n )\n .optional(),\n});\n\nexport async function loadProfile(profilePathOrName: string): Promise<ProfileConfig> {\n const candidates = [\n profilePathOrName,\n `${profilePathOrName}.json`,\n join(\".toolcapsule\", \"profiles\", `${profilePathOrName}.json`),\n join(\".github\", \"skills\", `${profilePathOrName}-mcp`, \"toolcapsule.config.json\"),\n ];\n const found = candidates.find((path) => existsSync(path));\n if (!found) throw new Error(`Profile not found: ${profilePathOrName}`);\n return profileSchema.parse(await readJson(found)) as ProfileConfig;\n}\n","import { mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport { dirname, resolve } from \"node:path\";\n\nexport async function readJson<T>(path: string): Promise<T> {\n return JSON.parse(await readFile(path, \"utf8\")) as T;\n}\n\nexport async function writeJson(path: string, value: unknown): Promise<void> {\n await mkdir(dirname(path), { recursive: true });\n await writeFile(path, `${JSON.stringify(value, null, 2)}\\n`);\n}\n\nexport async function writeText(path: string, value: string): Promise<void> {\n await mkdir(dirname(path), { recursive: true });\n await writeFile(path, value);\n}\n\nexport function abs(path: string): string {\n return resolve(process.cwd(), path);\n}\n","import type { McpTool } from \"../types.js\";\n\nexport function toolTitle(tool: McpTool): string {\n return tool.annotations?.title || tool.description?.split(/\\n/)[0]?.slice(0, 100) || tool.name;\n}\n\nexport function briefTool(tool: McpTool): string {\n const schema = tool.inputSchema as any;\n const required = Array.isArray(schema?.required) ? schema.required : [];\n const properties = schema?.properties && typeof schema.properties === \"object\" ? Object.keys(schema.properties) : [];\n const flags = [\n tool.annotations?.readOnlyHint ? \"read-only\" : undefined,\n tool.annotations?.destructiveHint ? \"destructive\" : undefined,\n ].filter(Boolean);\n return `- ${tool.name}: ${toolTitle(tool)}${flags.length ? ` [${flags.join(\", \")}]` : \"\"}${required.length ? ` required=${required.join(\"|\")}` : \"\"}${properties.length ? ` args=${properties.slice(0, 12).join(\"|\")}` : \"\"}`;\n}\n\nexport function briefTools(tools: McpTool[]): string {\n return tools.map(briefTool).join(\"\\n\");\n}\n","import type { McpTool } from \"../types.js\";\n\nexport function summarizeTool(tool: McpTool): unknown {\n const schema = tool.inputSchema as any;\n const properties = schema?.properties && typeof schema.properties === \"object\" ? schema.properties : {};\n return {\n name: tool.name,\n title: tool.annotations?.title,\n description: tool.description?.split(/\\n\\n/)[0]?.slice(0, 500),\n annotations: tool.annotations,\n required: Array.isArray(schema?.required) ? schema.required : [],\n arguments: Object.fromEntries(\n Object.entries(properties).map(([key, value]: [string, any]) => [\n key,\n { type: value?.type, enum: value?.enum, description: value?.description?.slice?.(0, 160) },\n ]),\n ),\n };\n}\n\nexport function summarizeTools(tools: McpTool[]): unknown[] {\n return tools.map(summarizeTool);\n}\n","import { mkdir, writeFile } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\nimport Handlebars from \"handlebars\";\nimport type { ProfileConfig } from \"../types.js\";\nimport { writeJson } from \"../utils/fs.js\";\n\nconst skillTemplate = `---\nname: {{skillName}}\ndescription: '{{description}}'\nargument-hint: 'MCP task, tool name, or args file to run'\n---\n\n# {{title}}\n\nThis skill wraps an MCP server as a lightweight, lazy-loaded, file-first workflow.\n\n## Why use this skill\n\n- Keep full MCP tool schemas out of the model context until needed.\n- Use brief tool summaries for everyday work.\n- Store large payloads in local files such as \\`args.json\\` or \\`content.md\\`.\n- Patch local artifacts and retry failed calls deterministically.\n\n## Commands\n\n\\`\\`\\`bash\ntoolcapsule tools {{profileName}} --brief\ntoolcapsule describe {{profileName}} <tool>\ntoolcapsule call {{profileName}} <tool> @args.json\ntoolcapsule retry <run-dir>\n\\`\\`\\`\n\n## Workflow\n\n1. Use \\`tools --brief\\` to find the likely tool.\n2. Use \\`describe <tool>\\` only when the brief schema is insufficient.\n3. Put complex arguments in a local JSON file.\n4. Call with \\`@args.json\\` and save the run.\n5. On failure, patch the local file and run \\`retry\\`.\n\n## Safety\n\n- Do not fabricate IDs, URLs, user IDs, or opaque tokens.\n- Prefer local files for large payloads.\n- Do not print secrets.\n- Review destructive tools before calling.\n`;\n\nexport type GenerateSkillOptions = {\n outputDir?: string;\n};\n\nexport async function generateSkill(profile: ProfileConfig, opts: GenerateSkillOptions = {}): Promise<string> {\n const skillName = profile.skill?.name || `${profile.name}-mcp`;\n const outputDir = opts.outputDir || join(\".github\", \"skills\", skillName);\n await mkdir(join(outputDir, \"scripts\"), { recursive: true });\n await mkdir(join(outputDir, \"runs\"), { recursive: true });\n\n const template = Handlebars.compile(skillTemplate);\n const description =\n profile.skill?.description ||\n `Use when operating tools from the ${profile.name} MCP server. Lazy-load schemas, call tools through local files, and retry failed calls by patching artifacts.`;\n const markdown = template({\n skillName,\n profileName: profile.name,\n title: `${profile.name} MCP Skill`,\n description: description.replace(/'/g, \"''\"),\n });\n await writeFile(join(outputDir, \"SKILL.md\"), markdown);\n await writeJson(join(outputDir, \"toolcapsule.config.json\"), profile);\n await writeFile(\n join(outputDir, \"scripts\", \"README.md\"),\n `# Scripts\\n\\nThis skill uses the project-level \\`toolcapsule\\` CLI.\\n`,\n );\n return outputDir;\n}\n\nexport async function writeProfile(path: string, profile: ProfileConfig): Promise<void> {\n await mkdir(dirname(path), { recursive: true });\n await writeJson(path, profile);\n}\n","import { mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport type { RunRecord } from \"../types.js\";\nimport { writeJson } from \"../utils/fs.js\";\n\nexport function createRunId(): string {\n return new Date().toISOString().replace(/[:.]/g, \"-\");\n}\n\nexport async function saveRun(baseDir: string, record: RunRecord): Promise<string> {\n const dir = join(baseDir, record.id);\n await mkdir(dir, { recursive: true });\n await writeJson(join(dir, \"run.json\"), record);\n await writeJson(join(dir, \"request.json\"), record.request);\n if (record.response !== undefined) await writeJson(join(dir, \"response.json\"), record.response);\n if (record.error) await writeFile(join(dir, \"error.txt\"), record.error);\n await writeFile(join(dir, \"command.txt\"), `${record.command}\\n`);\n return dir;\n}\n\nexport async function loadRun(runDir: string): Promise<RunRecord> {\n return JSON.parse(await readFile(join(runDir, \"run.json\"), \"utf8\")) as RunRecord;\n}\n","export function roughTokens(value: unknown): number {\n const text = typeof value === \"string\" ? value : JSON.stringify(value);\n return Math.ceil(text.length / 4);\n}\n\nexport function percentReduction(before: number, after: number): number {\n return before === 0 ? 0 : ((before - after) / before) * 100;\n}\n"],"mappings":"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","names":["readFile","join","resolve","mkdir","writeFile","dirname","join","join","mkdir","writeFile","dirname","mkdir","readFile","writeFile","join","join","mkdir","writeFile","readFile","join","readFile"]}
1
+ {"version":3,"sources":["../src/cli.ts","../src/mcp/client.ts","../src/profile.ts","../src/utils/fs.ts","../src/schema/brief.ts","../src/schema/summarize.ts","../src/skill/generator.ts","../src/runs/recorder.ts","../src/utils/tokens.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { readFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { cac } from \"cac\";\nimport pc from \"picocolors\";\nimport type { ProfileConfig } from \"./types.js\";\nimport { McpClient } from \"./mcp/client.js\";\nimport { loadProfile } from \"./profile.js\";\nimport { briefTools } from \"./schema/brief.js\";\nimport { summarizeTool, summarizeTools } from \"./schema/summarize.js\";\nimport { generateSkill, writeProfile } from \"./skill/generator.js\";\nimport { createRunId, loadRun, saveRun } from \"./runs/recorder.js\";\nimport { writeFile } from \"node:fs/promises\";\nimport { readJson } from \"./utils/fs.js\";\nimport { percentReduction, roughTokens } from \"./utils/tokens.js\";\n\nconst cli = cac(\"toolcapsule\");\n\nasync function readPackageVersion(): Promise<string> {\n try {\n const pkg = JSON.parse(await readFile(new URL(\"../package.json\", import.meta.url), \"utf8\")) as { version?: string };\n return pkg.version || \"0.0.0\";\n } catch {\n return \"0.0.0\";\n }\n}\n\nconst packageVersion = await readPackageVersion();\n\nasync function withClient<T>(profile: ProfileConfig, fn: (client: McpClient) => Promise<T>): Promise<T> {\n const client = new McpClient(profile);\n try {\n await client.init();\n return await fn(client);\n } finally {\n await client.close();\n }\n}\n\nfunction readArgsPath(raw: string): string {\n return raw.startsWith(\"@\") ? raw.slice(1) : raw;\n}\n\ncli\n .command(\"init <name>\", \"Create a profile and generated Agent Skill for an MCP server\")\n .option(\"--url <url>\", \"Remote MCP URL\")\n .option(\"--command <command>\", \"stdio MCP command\")\n .option(\"--arg <arg>\", \"stdio MCP argument, repeatable\", { type: [String] })\n .option(\"--output <dir>\", \"Skill output directory\")\n .action(async (name: string, options: { url?: string; command?: string; arg?: string[]; output?: string }) => {\n if (!options.url && !options.command) throw new Error(\"Provide --url for remote MCP or --command for stdio MCP\");\n const profile: ProfileConfig = options.url\n ? { name, transport: { type: \"remote\", url: options.url } }\n : { name, transport: { type: \"stdio\", command: options.command!, args: options.arg ?? [] } };\n await writeProfile(join(\".toolcapsule\", \"profiles\", `${name}.json`), profile);\n const out = await generateSkill(profile, options.output ? { outputDir: options.output } : {});\n console.log(pc.green(`Created profile and skill at ${out}`));\n });\n\ncli\n .command(\"tools <profile>\", \"List MCP tools\")\n .option(\"--brief\", \"Print compact tool summaries\")\n .option(\"--names\", \"Print tool names only\")\n .option(\"--json\", \"Print raw JSON\")\n .action(async (profileName: string, options: { brief?: boolean; names?: boolean; json?: boolean }) => {\n const profile = await loadProfile(profileName);\n const result = await withClient(profile, (client) => client.listTools());\n if (options.json) console.log(JSON.stringify(result, null, 2));\n else if (options.names) console.log(result.tools.map((tool) => tool.name).join(\"\\n\"));\n else console.log(briefTools(result.tools));\n });\n\ncli\n .command(\"describe <profile> <tool>\", \"Describe one MCP tool\")\n .option(\"--brief\", \"Print summarized schema instead of raw schema\")\n .action(async (profileName: string, toolName: string, options: { brief?: boolean }) => {\n const profile = await loadProfile(profileName);\n const result = await withClient(profile, (client) => client.listTools());\n const tool = result.tools.find((item) => item.name === toolName);\n if (!tool) throw new Error(`Tool not found: ${toolName}`);\n console.log(JSON.stringify(options.brief ? summarizeTool(tool) : tool, null, 2));\n });\n\ncli.command(\"schema <profile> <tool>\", \"Print a compact schema for one MCP tool\").action(async (profileName: string, toolName: string) => {\n const profile = await loadProfile(profileName);\n const result = await withClient(profile, (client) => client.listTools());\n const tool = result.tools.find((item) => item.name === toolName);\n if (!tool) throw new Error(`Tool not found: ${toolName}`);\n console.log(JSON.stringify(summarizeTool(tool), null, 2));\n});\n\ncli\n .command(\"call <profile> <tool> <args>\", \"Call an MCP tool with JSON args or @args.json\")\n .option(\"--save-run\", \"Save request, response, and command under runs/\")\n .action(async (profileName: string, toolName: string, argsRaw: string, options: { saveRun?: boolean }) => {\n const argsPath = readArgsPath(argsRaw);\n const toolArgs = argsRaw.startsWith(\"@\") ? await readJson(argsPath) : JSON.parse(argsRaw);\n const profile = await loadProfile(profileName);\n const request = { name: toolName, arguments: toolArgs };\n const command = `toolcapsule call ${profileName} ${toolName} ${argsRaw}`;\n const runId = createRunId();\n try {\n const response = await withClient(profile, (client) => client.callTool(toolName, toolArgs));\n console.log(JSON.stringify(response, null, 2));\n if (options.saveRun) {\n const dir = await saveRun(\"runs\", {\n id: runId,\n createdAt: new Date().toISOString(),\n profile: profileName,\n tool: toolName,\n argsFile: argsPath,\n status: \"success\",\n command,\n request,\n response,\n });\n console.error(pc.green(`Saved run: ${dir}`));\n }\n } catch (error) {\n if (options.saveRun) {\n const dir = await saveRun(\"runs\", {\n id: runId,\n createdAt: new Date().toISOString(),\n profile: profileName,\n tool: toolName,\n argsFile: argsPath,\n status: \"error\",\n command,\n request,\n error: error instanceof Error ? error.message : String(error),\n });\n console.error(pc.yellow(`Saved failed run: ${dir}`));\n }\n throw error;\n }\n });\n\ncli.command(\"retry <runDir>\", \"Retry a saved run, re-reading the args file\").action(async (runDir: string) => {\n const run = await loadRun(runDir);\n const toolArgs = await readJson(run.argsFile);\n const profile = await loadProfile(run.profile);\n const response = await withClient(profile, (client) => client.callTool(run.tool, toolArgs));\n console.log(JSON.stringify(response, null, 2));\n});\n\ncli.command(\"summarize <profile>\", \"Summarize all tools into compact JSON\").action(async (profileName: string) => {\n const profile = await loadProfile(profileName);\n const result = await withClient(profile, (client) => client.listTools());\n console.log(JSON.stringify(summarizeTools(result.tools), null, 2));\n});\n\ncli\n .command(\"benchmark <profile>\", \"Estimate schema savings for a profile\")\n .option(\"--markdown\", \"Print a Markdown report\")\n .option(\"--out <file>\", \"Write report to a file\")\n .action(async (profileName: string, options: { markdown?: boolean; out?: string }) => {\n const profile = await loadProfile(profileName);\n const result = await withClient(profile, (client) => client.listTools());\n const nativeTokens = roughTokens(result);\n const brief = summarizeTools(result.tools);\n const briefTokens = roughTokens(brief);\n const summary = {\n profile: profileName,\n tools: result.tools.length,\n nativeToolsRoughTokens: nativeTokens,\n summarizedToolsRoughTokens: briefTokens,\n estimatedReductionPct: Number(percentReduction(nativeTokens, briefTokens).toFixed(2)),\n };\n const output = options.markdown\n ? `# ToolCapsule benchmark: ${profileName}\\n\\n| Metric | Value |\\n|---|---:|\\n| MCP tools | ${summary.tools} |\\n| Native MCP schema rough tokens | ${summary.nativeToolsRoughTokens} |\\n| ToolCapsule summary rough tokens | ${summary.summarizedToolsRoughTokens} |\\n| Estimated reduction | ${summary.estimatedReductionPct}% |\\n\\n> Rough tokens are estimated from serialized schema length. Use this report to compare schema footprint before and after capsule summaries.\\n`\n : JSON.stringify(summary, null, 2);\n if (options.out) await writeFile(options.out, output);\n console.log(output);\n});\n\ncli.command(\"render-readme\", \"Print website hero copy snippets\").action(async () => {\n console.log(await readFile(new URL(\"../docs/hero-copy.md\", import.meta.url), \"utf8\"));\n});\n\ncli.help();\ncli.version(packageVersion);\n\ntry {\n cli.parse();\n} catch (error) {\n console.error(pc.red(error instanceof Error ? error.message : String(error)));\n process.exit(1);\n}\n","import { spawn, type ChildProcessWithoutNullStreams } from \"node:child_process\";\nimport { once } from \"node:events\";\nimport type { JsonRpcMessage, ProfileConfig, ToolsListResult } from \"../types.js\";\n\nexport type McpClientOptions = {\n timeoutMs?: number;\n};\n\nexport class McpClient {\n private child: ChildProcessWithoutNullStreams;\n private nextId = 1;\n private buffer = \"\";\n private pending = new Map<\n number,\n { resolve: (value: JsonRpcMessage) => void; reject: (error: Error) => void }\n >();\n private timeoutMs: number;\n\n constructor(profile: ProfileConfig, opts: McpClientOptions = {}) {\n this.timeoutMs = opts.timeoutMs ?? Number(process.env.TOOLCAPSULE_TIMEOUT_MS || \"45000\");\n if (profile.transport.type === \"remote\") {\n this.child = spawn(\"npx\", [\"-y\", \"mcp-remote\", profile.transport.url], {\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n });\n } else {\n this.child = spawn(profile.transport.command, profile.transport.args ?? [], {\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n });\n }\n this.child.stdout.setEncoding(\"utf8\");\n this.child.stdout.on(\"data\", (chunk: string) => this.onStdout(chunk));\n this.child.stderr.on(\"data\", (chunk: Buffer) => process.stderr.write(chunk));\n this.child.on(\"exit\", (code, signal) => {\n const error = new Error(`MCP process exited early (code=${code}, signal=${signal})`);\n for (const waiter of this.pending.values()) waiter.reject(error);\n this.pending.clear();\n });\n }\n\n private onStdout(chunk: string): void {\n this.buffer += chunk;\n let newlineIndex: number;\n while ((newlineIndex = this.buffer.indexOf(\"\\n\")) >= 0) {\n const line = this.buffer.slice(0, newlineIndex).trim();\n this.buffer = this.buffer.slice(newlineIndex + 1);\n if (!line) continue;\n let message: JsonRpcMessage;\n try {\n message = JSON.parse(line) as JsonRpcMessage;\n } catch {\n process.stderr.write(`${line}\\n`);\n continue;\n }\n if (typeof message.id === \"number\") {\n const waiter = this.pending.get(message.id);\n if (waiter) {\n this.pending.delete(message.id);\n waiter.resolve(message);\n }\n }\n }\n }\n\n async init(): Promise<void> {\n await this.request(\"initialize\", {\n protocolVersion: \"2025-03-26\",\n capabilities: {},\n clientInfo: { name: \"toolcapsule\", version: \"0.1.0\" },\n });\n this.notify(\"notifications/initialized\", {});\n }\n\n async request(method: string, params?: unknown): Promise<unknown> {\n const id = this.nextId++;\n const responsePromise = new Promise<JsonRpcMessage>((resolve, reject) => {\n this.pending.set(id, { resolve, reject });\n });\n this.child.stdin.write(`${JSON.stringify({ jsonrpc: \"2.0\", id, method, params })}\\n`);\n const response = await this.withTimeout(responsePromise, method);\n if (response.error) throw new Error(`${method} failed: ${JSON.stringify(response.error, null, 2)}`);\n return response.result;\n }\n\n notify(method: string, params?: unknown): void {\n this.child.stdin.write(`${JSON.stringify({ jsonrpc: \"2.0\", method, params })}\\n`);\n }\n\n async listTools(): Promise<ToolsListResult> {\n return (await this.request(\"tools/list\", {})) as ToolsListResult;\n }\n\n async callTool(name: string, args: unknown): Promise<unknown> {\n return await this.request(\"tools/call\", { name, arguments: args });\n }\n\n async close(): Promise<void> {\n if (!this.child.killed) this.child.kill();\n if (this.child.exitCode === null) await Promise.race([once(this.child, \"exit\"), new Promise((resolve) => setTimeout(resolve, 500))]);\n }\n\n private async withTimeout<T>(promise: Promise<T>, label: string): Promise<T> {\n let timer: NodeJS.Timeout | undefined;\n const timeout = new Promise<never>((_, reject) => {\n timer = setTimeout(() => reject(new Error(`${label} timed out after ${this.timeoutMs}ms`)), this.timeoutMs);\n });\n try {\n return await Promise.race([promise, timeout]);\n } finally {\n if (timer) clearTimeout(timer);\n }\n }\n}\n","import { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { z } from \"zod\";\nimport type { ProfileConfig } from \"./types.js\";\nimport { readJson } from \"./utils/fs.js\";\n\nconst profileSchema = z.object({\n name: z.string().min(1),\n transport: z.union([\n z.object({ type: z.literal(\"remote\"), url: z.string().url() }),\n z.object({ type: z.literal(\"stdio\"), command: z.string().min(1), args: z.array(z.string()).optional() }),\n ]),\n skill: z\n .object({\n name: z.string().optional(),\n description: z.string().optional(),\n })\n .optional(),\n shortcuts: z\n .record(\n z.object({\n tool: z.string(),\n description: z.string().optional(),\n args: z.record(z.enum([\"string\", \"file\", \"json\", \"boolean\", \"number\"])).optional(),\n }),\n )\n .optional(),\n});\n\nexport async function loadProfile(profilePathOrName: string): Promise<ProfileConfig> {\n const candidates = [\n profilePathOrName,\n `${profilePathOrName}.json`,\n join(\".toolcapsule\", \"profiles\", `${profilePathOrName}.json`),\n join(\".github\", \"skills\", `${profilePathOrName}-mcp`, \"toolcapsule.config.json\"),\n ];\n const found = candidates.find((path) => existsSync(path));\n if (!found) throw new Error(`Profile not found: ${profilePathOrName}`);\n return profileSchema.parse(await readJson(found)) as ProfileConfig;\n}\n","import { mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport { dirname, resolve } from \"node:path\";\n\nexport async function readJson<T>(path: string): Promise<T> {\n return JSON.parse(await readFile(path, \"utf8\")) as T;\n}\n\nexport async function writeJson(path: string, value: unknown): Promise<void> {\n await mkdir(dirname(path), { recursive: true });\n await writeFile(path, `${JSON.stringify(value, null, 2)}\\n`);\n}\n\nexport async function writeText(path: string, value: string): Promise<void> {\n await mkdir(dirname(path), { recursive: true });\n await writeFile(path, value);\n}\n\nexport function abs(path: string): string {\n return resolve(process.cwd(), path);\n}\n","import type { McpTool } from \"../types.js\";\n\nexport function toolTitle(tool: McpTool): string {\n return tool.annotations?.title || tool.description?.split(/\\n/)[0]?.slice(0, 100) || tool.name;\n}\n\nexport function briefTool(tool: McpTool): string {\n const schema = tool.inputSchema as any;\n const required = Array.isArray(schema?.required) ? schema.required : [];\n const properties = schema?.properties && typeof schema.properties === \"object\" ? Object.keys(schema.properties) : [];\n const flags = [\n tool.annotations?.readOnlyHint ? \"read-only\" : undefined,\n tool.annotations?.destructiveHint ? \"destructive\" : undefined,\n ].filter(Boolean);\n return `- ${tool.name}: ${toolTitle(tool)}${flags.length ? ` [${flags.join(\", \")}]` : \"\"}${required.length ? ` required=${required.join(\"|\")}` : \"\"}${properties.length ? ` args=${properties.slice(0, 12).join(\"|\")}` : \"\"}`;\n}\n\nexport function briefTools(tools: McpTool[]): string {\n return tools.map(briefTool).join(\"\\n\");\n}\n","import type { McpTool } from \"../types.js\";\n\nexport function summarizeTool(tool: McpTool): unknown {\n const schema = tool.inputSchema as any;\n const properties = schema?.properties && typeof schema.properties === \"object\" ? schema.properties : {};\n return {\n name: tool.name,\n title: tool.annotations?.title,\n description: tool.description?.split(/\\n\\n/)[0]?.slice(0, 500),\n annotations: tool.annotations,\n required: Array.isArray(schema?.required) ? schema.required : [],\n arguments: Object.fromEntries(\n Object.entries(properties).map(([key, value]: [string, any]) => [\n key,\n { type: value?.type, enum: value?.enum, description: value?.description?.slice?.(0, 160) },\n ]),\n ),\n };\n}\n\nexport function summarizeTools(tools: McpTool[]): unknown[] {\n return tools.map(summarizeTool);\n}\n","import { mkdir, writeFile } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\nimport Handlebars from \"handlebars\";\nimport type { ProfileConfig } from \"../types.js\";\nimport { writeJson } from \"../utils/fs.js\";\n\nconst skillTemplate = `---\nname: {{skillName}}\ndescription: '{{description}}'\nargument-hint: 'MCP task, tool name, or args file to run'\n---\n\n# {{title}}\n\nThis skill wraps an MCP server as a lightweight, lazy-loaded, file-first workflow.\n\n## Why use this skill\n\n- Keep full MCP tool schemas out of the model context until needed.\n- Use brief tool summaries for everyday work.\n- Store large payloads in local files such as \\`args.json\\` or \\`content.md\\`.\n- Patch local artifacts and retry failed calls deterministically.\n\n## Commands\n\n\\`\\`\\`bash\ntoolcapsule tools {{profileName}} --brief\ntoolcapsule describe {{profileName}} <tool>\ntoolcapsule call {{profileName}} <tool> @args.json\ntoolcapsule retry <run-dir>\n\\`\\`\\`\n\n## Workflow\n\n1. Use \\`tools --brief\\` to find the likely tool.\n2. Use \\`describe <tool>\\` only when the brief schema is insufficient.\n3. Put complex arguments in a local JSON file.\n4. Call with \\`@args.json\\` and save the run.\n5. On failure, patch the local file and run \\`retry\\`.\n\n## Safety\n\n- Do not fabricate IDs, URLs, user IDs, or opaque tokens.\n- Prefer local files for large payloads.\n- Do not print secrets.\n- Review destructive tools before calling.\n`;\n\nexport type GenerateSkillOptions = {\n outputDir?: string;\n};\n\nexport async function generateSkill(profile: ProfileConfig, opts: GenerateSkillOptions = {}): Promise<string> {\n const skillName = profile.skill?.name || `${profile.name}-mcp`;\n const outputDir = opts.outputDir || join(\".github\", \"skills\", skillName);\n await mkdir(join(outputDir, \"scripts\"), { recursive: true });\n await mkdir(join(outputDir, \"runs\"), { recursive: true });\n\n const template = Handlebars.compile(skillTemplate);\n const description =\n profile.skill?.description ||\n `Use when operating tools from the ${profile.name} MCP server. Lazy-load schemas, call tools through local files, and retry failed calls by patching artifacts.`;\n const markdown = template({\n skillName,\n profileName: profile.name,\n title: `${profile.name} MCP Skill`,\n description: description.replace(/'/g, \"''\"),\n });\n await writeFile(join(outputDir, \"SKILL.md\"), markdown);\n await writeJson(join(outputDir, \"toolcapsule.config.json\"), profile);\n await writeFile(\n join(outputDir, \"scripts\", \"README.md\"),\n `# Scripts\\n\\nThis skill uses the project-level \\`toolcapsule\\` CLI.\\n`,\n );\n return outputDir;\n}\n\nexport async function writeProfile(path: string, profile: ProfileConfig): Promise<void> {\n await mkdir(dirname(path), { recursive: true });\n await writeJson(path, profile);\n}\n","import { mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport type { RunRecord } from \"../types.js\";\nimport { writeJson } from \"../utils/fs.js\";\n\nexport function createRunId(): string {\n return new Date().toISOString().replace(/[:.]/g, \"-\");\n}\n\nexport async function saveRun(baseDir: string, record: RunRecord): Promise<string> {\n const dir = join(baseDir, record.id);\n await mkdir(dir, { recursive: true });\n await writeJson(join(dir, \"run.json\"), record);\n await writeJson(join(dir, \"request.json\"), record.request);\n if (record.response !== undefined) await writeJson(join(dir, \"response.json\"), record.response);\n if (record.error) await writeFile(join(dir, \"error.txt\"), record.error);\n await writeFile(join(dir, \"command.txt\"), `${record.command}\\n`);\n return dir;\n}\n\nexport async function loadRun(runDir: string): Promise<RunRecord> {\n return JSON.parse(await readFile(join(runDir, \"run.json\"), \"utf8\")) as RunRecord;\n}\n","export function roughTokens(value: unknown): number {\n const text = typeof value === \"string\" ? value : JSON.stringify(value);\n return Math.ceil(text.length / 4);\n}\n\nexport function percentReduction(before: number, after: number): number {\n return before === 0 ? 0 : ((before - after) / before) * 100;\n}\n"],"mappings":"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","names":["readFile","join","resolve","mkdir","writeFile","dirname","join","join","mkdir","writeFile","dirname","mkdir","readFile","writeFile","join","join","mkdir","writeFile","readFile","writeFile","readFile","join","writeFile"]}
package/docs/launch.md ADDED
@@ -0,0 +1,46 @@
1
+ # Launch notes
2
+
3
+ ## One-line pitch
4
+
5
+ Heavy MCP tools don't belong in your prompt. Put them in a ToolCapsule.
6
+
7
+ ## Short pitch
8
+
9
+ ToolCapsule turns schema-heavy MCP servers into lightweight, lazy-loaded Agent Skills with file-first calls and patch-and-retry recovery.
10
+
11
+ ## Announcement draft
12
+
13
+ MCP is becoming the standard way to connect agents to tools. But large MCP servers can quietly eat your context window with long tool descriptions and schemas.
14
+
15
+ ToolCapsule keeps MCP as the capability layer and turns heavy tools into compact Agent Skills. Large payloads live in files, every call can be recorded, and failed calls can be patched and retried without asking the model to regenerate everything.
16
+
17
+ Install:
18
+
19
+ ```bash
20
+ npm i -g toolcapsule
21
+ ```
22
+
23
+ Try:
24
+
25
+ ```bash
26
+ tcap init feishu --url https://mcp.example.com/mcp/xxx
27
+ tcap tools feishu --brief
28
+ tcap call feishu create-doc @args.json --save-run
29
+ ```
30
+
31
+ ## Suggested screenshot checklist
32
+
33
+ Ask a human/function-caller to capture:
34
+
35
+ 1. Hero section at `https://toolcapsule.studio` or `https://toolcapsule.vercel.app`.
36
+ 2. Terminal showing `tcap tools feishu --brief`.
37
+ 3. Terminal showing a saved run directory.
38
+ 4. Before/after graphic: native MCP schema in prompt vs ToolCapsule local artifacts.
39
+
40
+ ## Suggested GIF flow
41
+
42
+ 1. Run `tcap call ... --save-run`.
43
+ 2. Show failure or saved run artifacts.
44
+ 3. Patch `args.json`.
45
+ 4. Run `tcap retry runs/<id>`.
46
+ 5. Show success.
@@ -0,0 +1,284 @@
1
+ # Next Steps
2
+
3
+ ToolCapsule has reached a public alpha loop: GitHub repository, Vercel site, npm package, release workflow, and project skill are in place.
4
+
5
+ This document tracks what should happen next.
6
+
7
+ ## 1. Immediate follow-up
8
+
9
+ ### 1.1 Wait for custom domain HTTPS
10
+
11
+ Check:
12
+
13
+ ```bash
14
+ curl -I https://toolcapsule.studio
15
+ ```
16
+
17
+ Target:
18
+
19
+ ```text
20
+ HTTP/2 200
21
+ server: Vercel
22
+ ```
23
+
24
+ If HTTPS is unstable shortly after domain purchase, wait for registry, DNS, and Vercel certificate propagation.
25
+
26
+ ### 1.2 Check the npm package page
27
+
28
+ Package:
29
+
30
+ ```text
31
+ https://www.npmjs.com/package/toolcapsule
32
+ ```
33
+
34
+ Verify:
35
+
36
+ - README renders correctly.
37
+ - Install command is clear.
38
+ - `toolcapsule` and `tcap` binaries are listed.
39
+ - Homepage points to `https://toolcapsule.studio`.
40
+
41
+ ### 1.3 Improve the GitHub Release note
42
+
43
+ Release:
44
+
45
+ ```text
46
+ https://github.com/RainSunMe/toolcapsule/releases/tag/v0.1.0-alpha.0
47
+ ```
48
+
49
+ Add a better release note with:
50
+
51
+ - What ToolCapsule is.
52
+ - Install command.
53
+ - Highlights.
54
+ - Known limitations.
55
+
56
+ ## 2. Product usability
57
+
58
+ ### 2.1 Build a real Feishu demo
59
+
60
+ The Feishu example should become a complete demo:
61
+
62
+ ```text
63
+ examples/feishu/
64
+ README.md
65
+ create-doc.args.json
66
+ update-doc.args.json
67
+ demo.md
68
+ screenshots/
69
+ ```
70
+
71
+ The demo should show:
72
+
73
+ ```bash
74
+ tcap init feishu --url ...
75
+ tcap tools feishu --brief
76
+ tcap call feishu create-doc @create-doc.args.json --save-run
77
+ tcap retry runs/...
78
+ ```
79
+
80
+ Goal: a new user should understand MCP → Skill → file-first call → patch-and-retry in one example.
81
+
82
+ ### 2.2 Add shortcuts
83
+
84
+ Current form:
85
+
86
+ ```bash
87
+ tcap call feishu create-doc @args.json
88
+ ```
89
+
90
+ Future forms:
91
+
92
+ ```bash
93
+ tcap shortcut create-doc --title "..." --markdown-file doc.md
94
+ ```
95
+
96
+ or:
97
+
98
+ ```bash
99
+ tcap run feishu:create-doc --title "..." --file doc.md
100
+ ```
101
+
102
+ Shortcuts are important for turning ToolCapsule from a generic MCP wrapper into a workflow tool.
103
+
104
+ ### 2.3 Improve schema summaries
105
+
106
+ Current `--brief` summaries are basic.
107
+
108
+ Future command:
109
+
110
+ ```bash
111
+ tcap schema feishu create-doc --brief
112
+ ```
113
+
114
+ Expected output:
115
+
116
+ ```text
117
+ required:
118
+ - title: string
119
+ - markdown: string
120
+
121
+ optional:
122
+ - wiki_node
123
+ - folder_token
124
+
125
+ warnings:
126
+ - do not fabricate IDs
127
+ - use search-user before mention
128
+ ```
129
+
130
+ This directly supports the core promise: keep heavy MCP schemas out of the prompt.
131
+
132
+ ### 2.4 Improve benchmark output
133
+
134
+ Current command:
135
+
136
+ ```bash
137
+ tcap benchmark <profile>
138
+ ```
139
+
140
+ Future output should include:
141
+
142
+ ```text
143
+ Native MCP schema tokens: 4,224
144
+ ToolCapsule brief tokens: 142
145
+ Estimated reduction: 96.6%
146
+ ```
147
+
148
+ It should also generate:
149
+
150
+ ```text
151
+ toolcapsule-report.md
152
+ ```
153
+
154
+ Goal: users can measure context savings on their own MCP server.
155
+
156
+ ## 3. Open-source infrastructure
157
+
158
+ ### 3.1 Add Changesets
159
+
160
+ Recommended:
161
+
162
+ ```bash
163
+ pnpm add -D @changesets/cli
164
+ pnpm changeset init
165
+ ```
166
+
167
+ This will standardize version bumps and changelog updates.
168
+
169
+ ### 3.2 Add README badges
170
+
171
+ Add badges for:
172
+
173
+ - npm version;
174
+ - CI status;
175
+ - license;
176
+ - GitHub stars.
177
+
178
+ ### 3.3 Harden SECURITY docs
179
+
180
+ Because ToolCapsule can call tools, store artifacts, and connect to MCP servers, clarify:
181
+
182
+ - Do not commit secrets.
183
+ - Run artifacts may contain sensitive data.
184
+ - `/runs/` is ignored by default.
185
+ - Generated files should be reviewed before sharing.
186
+ - Enterprise users should audit local artifacts.
187
+
188
+ ## 4. Growth and launch
189
+
190
+ ### 4.1 Write a launch blog post
191
+
192
+ Suggested title:
193
+
194
+ ```text
195
+ Your MCP tools are eating your context window
196
+ ```
197
+
198
+ Suggested structure:
199
+
200
+ 1. MCP is great.
201
+ 2. Heavy MCP tools are expensive.
202
+ 3. Skills are the lazy-loading layer.
203
+ 4. ToolCapsule = file-first MCP workflows.
204
+ 5. Patch-and-retry.
205
+ 6. Benchmark: 88.4% fewer input tokens.
206
+ 7. Install.
207
+
208
+ ### 4.2 Create a comparison graphic
209
+
210
+ Core diagram:
211
+
212
+ ```text
213
+ Native MCP:
214
+ Prompt + full tools schema + user message
215
+
216
+ ToolCapsule:
217
+ Prompt + compact Skill + args.json
218
+ ```
219
+
220
+ Add the headline:
221
+
222
+ ```text
223
+ 88.4% fewer input tokens
224
+ ```
225
+
226
+ ### 4.3 Launch channels
227
+
228
+ Suggested order:
229
+
230
+ 1. GitHub README polish.
231
+ 2. npm alpha.
232
+ 3. Stable website.
233
+ 4. X / Twitter.
234
+ 5. Hacker News.
235
+ 6. Reddit r/LocalLLaMA.
236
+ 7. MCP / Claude Code / Cursor communities.
237
+ 8. Chinese channels: V2EX, Jike, Juejin.
238
+
239
+ ## 5. Suggested priority
240
+
241
+ ### Next session
242
+
243
+ 1. Wait for `toolcapsule.studio` HTTPS to stabilize.
244
+ 2. Add README badges.
245
+ 3. Improve GitHub Release note.
246
+ 4. Build the Feishu demo.
247
+ 5. Add Changesets.
248
+
249
+ ### First week
250
+
251
+ 1. Improve `benchmark`.
252
+ 2. Build schema summary command.
253
+ 3. Add shortcut system.
254
+ 4. Write launch blog.
255
+ 5. Prepare first public announcement.
256
+
257
+ ### v0.2 goal
258
+
259
+ ```text
260
+ ToolCapsule is usable by someone who has never seen the original conversation.
261
+ ```
262
+
263
+ To reach that:
264
+
265
+ - one clear demo;
266
+ - one complete MCP → Skill → call → retry flow;
267
+ - one benchmark report;
268
+ - stronger README;
269
+ - stronger docs.
270
+
271
+ ## 6. Most important next task
272
+
273
+ If only one thing can be done next, do this:
274
+
275
+ > Build a real Feishu demo with a README and screenshots/GIF.
276
+
277
+ The core concept is new. Users need to see the difference:
278
+
279
+ ```text
280
+ Before: the model rewrites the entire tool call.
281
+ After: patch args.json, then run tcap retry.
282
+ ```
283
+
284
+ That demo will explain ToolCapsule better than a long abstract description.
@@ -8,7 +8,7 @@ Before publishing v0.1.0:
8
8
  - [ ] Connect the repository to Vercel.
9
9
  - [ ] Point `toolcapsule.studio` to Vercel.
10
10
  - [ ] Enable GitHub Discussions.
11
- - [ ] Add `NPM_TOKEN` or configure npm trusted publishing.
11
+ - [ ] Configure npm Trusted Publishing for `release.yml` and environment `npm`.
12
12
  - [ ] Review the security model for local command execution.
13
13
  - [ ] Run `pnpm run ci`.
14
14
  - [ ] Run CLI smoke tests with a mock MCP server.
@@ -0,0 +1,38 @@
1
+ # Releasing
2
+
3
+ ToolCapsule uses npm Trusted Publishing and GitHub Releases.
4
+
5
+ ## One-time setup
6
+
7
+ On npm, configure Trusted Publishing for the `toolcapsule` package:
8
+
9
+ - Repository: `RainSunMe/toolcapsule`
10
+ - Workflow: `release.yml`
11
+ - Environment: `npm`
12
+
13
+ On GitHub, create an Environment named `npm`.
14
+
15
+ ## Release steps
16
+
17
+ 1. Create a changeset with `pnpm changeset` for normal changes, or update `package.json` / `CHANGELOG.md` manually for urgent alpha releases.
18
+ 2. Ensure `CHANGELOG.md` describes the release.
19
+ 3. Commit and push to `main`.
20
+ 4. Create and push a tag:
21
+
22
+ ```bash
23
+ git tag v0.1.0-alpha.1
24
+ git push origin v0.1.0-alpha.1
25
+ ```
26
+
27
+ The `Release` workflow will:
28
+
29
+ 1. install dependencies;
30
+ 2. run CI;
31
+ 3. publish to npm with provenance through Trusted Publishing;
32
+ 4. create a GitHub Release.
33
+
34
+ ## Notes
35
+
36
+ - Do not store `NPM_TOKEN` unless Trusted Publishing is unavailable.
37
+ - The workflow requires `permissions.id-token: write`.
38
+ - The GitHub Environment name must match npm Trusted Publishing exactly.
@@ -1,9 +1,60 @@
1
- # Feishu example
1
+ # Feishu / Lark demo
2
+
3
+ This demo shows the intended ToolCapsule workflow for a heavy document MCP server.
4
+
5
+ ## 1. Initialize a profile
6
+
7
+ ```bash
8
+ tcap init feishu --url https://mcp.feishu.cn/mcp/your-endpoint
9
+ ```
10
+
11
+ This creates:
12
+
13
+ ```text
14
+ .toolcapsule/profiles/feishu.json
15
+ .github/skills/feishu-mcp/SKILL.md
16
+ ```
17
+
18
+ ## 2. Discover tools briefly
2
19
 
3
20
  ```bash
4
- toolcapsule init feishu --url https://mcp.feishu.cn/mcp/your-endpoint
5
21
  tcap tools feishu --brief
6
- tcap call feishu create-doc @examples/feishu/args.create-doc.json --save-run
7
22
  ```
8
23
 
9
- Use a test folder or wiki space for live writes.
24
+ Use this instead of loading the full MCP schema into the prompt.
25
+
26
+ ## 3. Inspect one tool only when needed
27
+
28
+ ```bash
29
+ tcap schema feishu create-doc
30
+ ```
31
+
32
+ ## 4. Call with local artifacts
33
+
34
+ ```bash
35
+ tcap call feishu create-doc @examples/feishu/create-doc.args.json --save-run
36
+ ```
37
+
38
+ For large documents, keep the Markdown in `demo.md`, patch it locally, then copy/update the JSON payload before retrying.
39
+
40
+ ## 5. Patch and retry
41
+
42
+ If the call fails:
43
+
44
+ ```bash
45
+ # edit examples/feishu/create-doc.args.json or examples/feishu/demo.md
46
+ tcap retry runs/<run-id>
47
+ ```
48
+
49
+ ## Files
50
+
51
+ | File | Purpose |
52
+ |---|---|
53
+ | `demo.md` | Long Markdown payload example |
54
+ | `create-doc.args.json` | Create document call args |
55
+ | `update-doc.args.json` | Update document call args |
56
+ | `args.create-doc.json` | Legacy minimal args example |
57
+
58
+ ## Safety
59
+
60
+ Use a test folder or wiki space for live writes. Do not commit private MCP endpoints, tokens, user IDs, or document URLs.
@@ -0,0 +1,4 @@
1
+ {
2
+ "title": "ToolCapsule Feishu demo",
3
+ "markdown": "# ToolCapsule Feishu demo\n\nThis demo uses a local args file. For larger documents, put the Markdown in demo.md and copy it into this field before calling create-doc."
4
+ }
@@ -0,0 +1,21 @@
1
+ # ToolCapsule Feishu demo document
2
+
3
+ This file is intentionally stored as a local artifact.
4
+
5
+ ## Why this matters
6
+
7
+ Large Feishu documents often contain Markdown, tables, diagrams, mentions, and other structured content. If a write fails, the model should not regenerate the entire tool call.
8
+
9
+ ## Patch-and-retry demo
10
+
11
+ 1. Put this Markdown in a local file.
12
+ 2. Reference it from `create-doc.args.json`.
13
+ 3. Call the MCP tool with `tcap call ... --save-run`.
14
+ 4. If Feishu rejects a block, patch this file.
15
+ 5. Run `tcap retry runs/<id>`.
16
+
17
+ ## Checklist
18
+
19
+ - [x] Schema stays out of the prompt by default.
20
+ - [x] Payload lives in a file.
21
+ - [x] Failed calls are patchable.
@@ -0,0 +1,5 @@
1
+ {
2
+ "doc_id": "https://example.feishu.cn/docx/replace-with-your-doc-id",
3
+ "mode": "append",
4
+ "markdown": "## Patch-and-retry update\n\nThis update came from a local args file."
5
+ }
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "toolcapsule",
3
- "version": "0.1.0-alpha.0",
3
+ "version": "0.1.0-alpha.2",
4
4
  "description": "File-first, patchable Agent Skills for heavy MCP tools.",
5
5
  "type": "module",
6
6
  "bin": {
7
- "toolcapsule": "./dist/cli.js",
8
- "tcap": "./dist/cli.js"
7
+ "toolcapsule": "dist/cli.js",
8
+ "tcap": "dist/cli.js"
9
9
  },
10
10
  "files": [
11
11
  "dist",
@@ -22,6 +22,8 @@
22
22
  "lint": "eslint .",
23
23
  "format": "prettier --write .",
24
24
  "format:check": "prettier --check .",
25
+ "changeset": "changeset",
26
+ "version-packages": "changeset version",
25
27
  "ci": "tsc --noEmit && eslint . && vitest run && tsup",
26
28
  "smoke": "node dist/cli.js --help"
27
29
  },
@@ -61,6 +63,7 @@
61
63
  "zod": "^3.24.1"
62
64
  },
63
65
  "devDependencies": {
66
+ "@changesets/cli": "^2.31.0",
64
67
  "@eslint/js": "^9.18.0",
65
68
  "@types/node": "^22.10.7",
66
69
  "eslint": "^9.18.0",