frontmcp 1.2.1 → 1.4.0

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.
Files changed (114) hide show
  1. package/README.md +38 -29
  2. package/package.json +4 -4
  3. package/src/commands/build/exec/bin-meta.d.ts +49 -0
  4. package/src/commands/build/exec/bin-meta.js +68 -0
  5. package/src/commands/build/exec/bin-meta.js.map +1 -0
  6. package/src/commands/build/exec/cli-runtime/generate-cli-entry.js +195 -3
  7. package/src/commands/build/exec/cli-runtime/generate-cli-entry.js.map +1 -1
  8. package/src/commands/build/exec/cli-runtime/plugin-emitter.d.ts +160 -0
  9. package/src/commands/build/exec/cli-runtime/plugin-emitter.js +512 -0
  10. package/src/commands/build/exec/cli-runtime/plugin-emitter.js.map +1 -0
  11. package/src/commands/build/exec/cli-runtime/schema-extractor.d.ts +13 -1
  12. package/src/commands/build/exec/cli-runtime/schema-extractor.js +29 -3
  13. package/src/commands/build/exec/cli-runtime/schema-extractor.js.map +1 -1
  14. package/src/commands/build/exec/cli-runtime/skill-md-compose.d.ts +25 -0
  15. package/src/commands/build/exec/cli-runtime/skill-md-compose.js +63 -0
  16. package/src/commands/build/exec/cli-runtime/skill-md-compose.js.map +1 -0
  17. package/src/commands/build/exec/index.js +26 -0
  18. package/src/commands/build/exec/index.js.map +1 -1
  19. package/src/commands/build/exec/runner-script.js +16 -4
  20. package/src/commands/build/exec/runner-script.js.map +1 -1
  21. package/src/commands/dev/bridge/child-supervisor.d.ts +48 -0
  22. package/src/commands/dev/bridge/child-supervisor.js +228 -0
  23. package/src/commands/dev/bridge/child-supervisor.js.map +1 -0
  24. package/src/commands/dev/bridge/errors.d.ts +23 -0
  25. package/src/commands/dev/bridge/errors.js +34 -0
  26. package/src/commands/dev/bridge/errors.js.map +1 -0
  27. package/src/commands/dev/bridge/index.d.ts +30 -0
  28. package/src/commands/dev/bridge/index.js +220 -0
  29. package/src/commands/dev/bridge/index.js.map +1 -0
  30. package/src/commands/dev/bridge/log.d.ts +29 -0
  31. package/src/commands/dev/bridge/log.js +82 -0
  32. package/src/commands/dev/bridge/log.js.map +1 -0
  33. package/src/commands/dev/bridge/state-machine.d.ts +56 -0
  34. package/src/commands/dev/bridge/state-machine.js +245 -0
  35. package/src/commands/dev/bridge/state-machine.js.map +1 -0
  36. package/src/commands/dev/bridge/stdio-framer.d.ts +47 -0
  37. package/src/commands/dev/bridge/stdio-framer.js +128 -0
  38. package/src/commands/dev/bridge/stdio-framer.js.map +1 -0
  39. package/src/commands/dev/bridge/upstream-client.d.ts +49 -0
  40. package/src/commands/dev/bridge/upstream-client.js +159 -0
  41. package/src/commands/dev/bridge/upstream-client.js.map +1 -0
  42. package/src/commands/dev/bridge/watcher.d.ts +30 -0
  43. package/src/commands/dev/bridge/watcher.js +87 -0
  44. package/src/commands/dev/bridge/watcher.js.map +1 -0
  45. package/src/commands/dev/dev.d.ts +34 -1
  46. package/src/commands/dev/dev.js +168 -14
  47. package/src/commands/dev/dev.js.map +1 -1
  48. package/src/commands/dev/inspector.d.ts +13 -1
  49. package/src/commands/dev/inspector.js +77 -3
  50. package/src/commands/dev/inspector.js.map +1 -1
  51. package/src/commands/dev/port.d.ts +23 -0
  52. package/src/commands/dev/port.js +87 -0
  53. package/src/commands/dev/port.js.map +1 -0
  54. package/src/commands/dev/register.d.ts +1 -1
  55. package/src/commands/dev/register.js +28 -4
  56. package/src/commands/dev/register.js.map +1 -1
  57. package/src/commands/dev/test.d.ts +26 -1
  58. package/src/commands/dev/test.js +181 -64
  59. package/src/commands/dev/test.js.map +1 -1
  60. package/src/commands/eject/mcp-client.d.ts +25 -0
  61. package/src/commands/eject/mcp-client.js +74 -0
  62. package/src/commands/eject/mcp-client.js.map +1 -0
  63. package/src/commands/eject/register.d.ts +9 -0
  64. package/src/commands/eject/register.js +56 -0
  65. package/src/commands/eject/register.js.map +1 -0
  66. package/src/commands/install/install-claude-plugin.d.ts +13 -0
  67. package/src/commands/install/install-claude-plugin.js +327 -0
  68. package/src/commands/install/install-claude-plugin.js.map +1 -0
  69. package/src/commands/install/register.d.ts +16 -0
  70. package/src/commands/install/register.js +70 -0
  71. package/src/commands/install/register.js.map +1 -0
  72. package/src/commands/scaffold/create.js +52 -8
  73. package/src/commands/scaffold/create.js.map +1 -1
  74. package/src/commands/skills/from-entry.d.ts +31 -0
  75. package/src/commands/skills/from-entry.js +68 -0
  76. package/src/commands/skills/from-entry.js.map +1 -0
  77. package/src/commands/skills/install.d.ts +12 -0
  78. package/src/commands/skills/install.js +173 -8
  79. package/src/commands/skills/install.js.map +1 -1
  80. package/src/commands/skills/register.js +7 -3
  81. package/src/commands/skills/register.js.map +1 -1
  82. package/src/config/frontmcp-config.loader.d.ts +28 -0
  83. package/src/config/frontmcp-config.loader.js +146 -67
  84. package/src/config/frontmcp-config.loader.js.map +1 -1
  85. package/src/config/frontmcp-config.resolve.d.ts +67 -0
  86. package/src/config/frontmcp-config.resolve.js +118 -0
  87. package/src/config/frontmcp-config.resolve.js.map +1 -0
  88. package/src/config/frontmcp-config.schema.d.ts +207 -0
  89. package/src/config/frontmcp-config.schema.js +217 -1
  90. package/src/config/frontmcp-config.schema.js.map +1 -1
  91. package/src/config/frontmcp-config.types.d.ts +133 -0
  92. package/src/config/frontmcp-config.types.js.map +1 -1
  93. package/src/config/index.d.ts +2 -1
  94. package/src/config/index.js +3 -1
  95. package/src/config/index.js.map +1 -1
  96. package/src/core/args.d.ts +13 -0
  97. package/src/core/args.js.map +1 -1
  98. package/src/core/bridge.js +39 -0
  99. package/src/core/bridge.js.map +1 -1
  100. package/src/core/cli.d.ts +0 -6
  101. package/src/core/cli.js +23 -3
  102. package/src/core/cli.js.map +1 -1
  103. package/src/core/help.d.ts +1 -1
  104. package/src/core/help.js +27 -6
  105. package/src/core/help.js.map +1 -1
  106. package/src/core/program.d.ts +1 -1
  107. package/src/core/program.js +56 -12
  108. package/src/core/program.js.map +1 -1
  109. package/src/core/project-commands.d.ts +44 -0
  110. package/src/core/project-commands.js +216 -0
  111. package/src/core/project-commands.js.map +1 -0
  112. package/src/core/tsconfig.d.ts +20 -0
  113. package/src/core/tsconfig.js +41 -2
  114. package/src/core/tsconfig.js.map +1 -1
package/README.md CHANGED
@@ -7,10 +7,10 @@
7
7
  </picture>
8
8
  <hr>
9
9
 
10
- **The TypeScript way to build MCP servers with decorators, DI, and Streamable HTTP.**
10
+ **The production-grade, TypeScript-first framework for building MCP servers decorators, DI, auth, and Streamable HTTP, batteries included.**
11
11
 
12
12
  [![NPM - @frontmcp/sdk](https://img.shields.io/npm/v/@frontmcp/sdk.svg?v=2)](https://www.npmjs.com/package/@frontmcp/sdk)
13
- [![Node](https://img.shields.io/badge/node-%3E%3D22-339933?logo=node.js&logoColor=white)](https://nodejs.org)
13
+ [![Node](https://img.shields.io/badge/node-%3E%3D24-339933?logo=node.js&logoColor=white)](https://nodejs.org)
14
14
  [![License](https://img.shields.io/github/license/agentfront/frontmcp.svg?v=1)](https://github.com/agentfront/frontmcp/blob/main/LICENSE)
15
15
  [![Snyk](https://snyk.io/test/github/agentfront/frontmcp/badge.svg)](https://snyk.io/test/github/agentfront/frontmcp)
16
16
 
@@ -20,12 +20,17 @@
20
20
 
21
21
  ---
22
22
 
23
- FrontMCP is a **TypeScript-first framework** for the [Model Context Protocol (MCP)](https://modelcontextprotocol.io).
24
- You write clean, typed code; FrontMCP handles the protocol, transport, DI, session/auth, and execution flow.
23
+ FrontMCP turns the [Model Context Protocol](https://modelcontextprotocol.io) into a
24
+ typed, declarative framework. You write clean `@Tool`, `@Resource`, and `@App`
25
+ classes; FrontMCP handles the protocol, transport, dependency injection, sessions,
26
+ auth, and execution flow — and the **same server runs locally and ships to
27
+ production unchanged**.
25
28
 
26
29
  ```ts
27
30
  import 'reflect-metadata';
31
+
28
32
  import { FrontMcp, LogLevel } from '@frontmcp/sdk';
33
+
29
34
  import HelloApp from './hello.app';
30
35
 
31
36
  @FrontMcp({
@@ -37,6 +42,14 @@ import HelloApp from './hello.app';
37
42
  export default class Server {}
38
43
  ```
39
44
 
45
+ ## Why FrontMCP
46
+
47
+ - **Typed by default** — decorators + Zod schemas give end-to-end types from input to output, with editor autocomplete and compile-time checks.
48
+ - **Batteries included** — auth (OAuth/JWKS/DCR), sessions, transport, discovery, and DI are built in, not bolted on.
49
+ - **Ship anywhere** — one codebase deploys to Node, Vercel, AWS Lambda, Cloudflare Workers, or a serverless bundle.
50
+ - **Production-minded** — stateful/stateless sessions, high-availability transport, structured observability, and a 95%+ tested core.
51
+ - **Extensible** — plugins, lifecycle hooks, OpenAPI adapters, and external MCP sub-apps when you outgrow the defaults.
52
+
40
53
  ## Installation
41
54
 
42
55
  **Node.js 24+** required.
@@ -50,34 +63,30 @@ npm i -D frontmcp @types/node@^24
50
63
  npx frontmcp init
51
64
  ```
52
65
 
53
- > Full setup guide: [Installation][docs-install]
66
+ > Full setup guide: [Installation][docs-install] &middot; [Quickstart][docs-quickstart]
54
67
 
55
68
  ## Capabilities
56
69
 
57
- | Capability | Description | Docs |
58
- | -------------------- | ------------------------------------------------------------------------------- | ------------------------------- |
59
- | **@FrontMcp Server** | Decorator-configured server with info, apps, HTTP, logging, session, auth | [Server][docs-server] |
60
- | **@App** | Organizational units grouping tools, resources, prompts with optional isolation | [Apps][docs-apps] |
61
- | **@Tool** | Typed actions with Zod schemas — class or function style | [Tools][docs-tools] |
62
- | **@Resource** | Read-only data exposure with static and template URIs | [Resources][docs-resources] |
63
- | **@Prompt** | Reusable message templates returning `GetPromptResult` | [Prompts][docs-prompts] |
64
- | **@Agent** | Orchestrated multi-step tool chains | [Agents][docs-agents] |
65
- | **Elicitation** | Request structured user input mid-flow | [Elicitation][docs-elicitation] |
66
- | **Skills** | HTTP-discoverable tool manifests for agent marketplaces | [Skills][docs-skills] |
67
- | **Discovery** | Automatic capability advertisement for MCP clients | [Discovery][docs-discovery] |
68
- | **Authentication** | Remote OAuth, Local OAuth, JWKS, DCR, per-app auth | [Authentication][docs-auth] |
69
- | **Sessions** | Stateful/stateless session modes with JWT or UUID transport IDs | [Server][docs-server] |
70
- | **Direct Client** | In-process `create()`, `connect()`, `connectOpenAI()`, `connectClaude()` | [Direct Client][docs-direct] |
71
- | **Transport** | Streamable HTTP + SSE with session headers | [Transport][docs-transport] |
72
- | **Ext-Apps** | Mount external MCP servers as sub-apps | [Ext-Apps][docs-ext-apps] |
73
- | **Hooks** | 5 hook families: tool, list-tools, HTTP, resource, prompt | [Hooks][docs-hooks] |
74
- | **Providers / DI** | Scoped dependency injection with GLOBAL and CONTEXT scopes | [Providers][docs-providers] |
75
- | **Plugins** | Cache, Remember, CodeCall, Dashboard — or build your own | [Plugins][docs-plugins] |
76
- | **Adapters** | Generate tools from OpenAPI specs | [Adapters][docs-adapters] |
77
- | **Testing** | E2E fixtures, matchers, HTTP mocking for MCP servers | [Testing][docs-testing] |
78
- | **UI Library** | HTML/React widgets, SSR, MCP Bridge, web components | [UI][docs-ui] |
79
- | **CLI** | `create`, `init`, `dev`, `build`, `inspector`, `doctor` | [CLI][docs-install] |
80
- | **Deployment** | Local dev, production builds, version alignment | [Deployment][docs-deploy] |
70
+ **Build** decorator-configured [`@FrontMcp` server][docs-server] and [`@App`][docs-apps]
71
+ domains; typed [`@Tool`][docs-tools], [`@Resource`][docs-resources], and
72
+ [`@Prompt`][docs-prompts] primitives; [`@Agent`][docs-agents] multi-step chains; and
73
+ scoped [Providers / DI][docs-providers].
74
+
75
+ **Secure** [Remote & Local OAuth, JWKS, DCR, per-app auth][docs-auth] with
76
+ stateful / stateless [sessions][docs-server] (JWT or UUID transport IDs).
77
+
78
+ **Connect & operate** [Streamable HTTP + SSE transport][docs-transport],
79
+ capability [discovery][docs-discovery], [elicitation][docs-elicitation],
80
+ [hooks][docs-hooks], HTTP-discoverable [skills][docs-skills],
81
+ [external MCP sub-apps][docs-ext-apps], an in-process [Direct Client][docs-direct]
82
+ (`connectOpenAI` / `connectClaude`), and first-class [deployment][docs-deploy].
83
+
84
+ **Extend & tooling** official [plugins][docs-plugins] (Cache, Remember, CodeCall,
85
+ Dashboard), the [OpenAPI adapter][docs-adapters], a [UI library][docs-ui] (HTML/React
86
+ widgets, SSR, MCP Bridge), an [E2E testing framework][docs-testing], and a
87
+ [CLI][docs-install] (`create`, `init`, `dev`, `build`, `inspect`, `doctor`).
88
+
89
+ Full reference: **[docs.agentfront.dev/frontmcp][docs-home]**
81
90
 
82
91
  ## Packages
83
92
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "frontmcp",
3
- "version": "1.2.1",
3
+ "version": "1.4.0",
4
4
  "description": "FrontMCP command line interface",
5
5
  "author": "AgentFront <info@agentfront.dev>",
6
6
  "homepage": "https://docs.agentfront.dev",
@@ -31,9 +31,9 @@
31
31
  },
32
32
  "dependencies": {
33
33
  "@clack/prompts": "^0.10.0",
34
- "@frontmcp/lazy-zod": "1.2.1",
35
- "@frontmcp/utils": "1.2.1",
36
- "@frontmcp/skills": "1.2.1",
34
+ "@frontmcp/lazy-zod": "1.4.0",
35
+ "@frontmcp/utils": "1.4.0",
36
+ "@frontmcp/skills": "1.4.0",
37
37
  "commander": "^13.0.0",
38
38
  "tslib": "^2.3.0",
39
39
  "vectoriadb": "^2.2.0",
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Issue #411 — write `bin-meta.json` next to the CLI bundle. The per-bin
3
+ * `<bin> install -p claude|codex` reads this sidecar at runtime to drive
4
+ * the shared plugin-emitter without re-running schema extraction.
5
+ *
6
+ * Shape:
7
+ * {
8
+ * "name": "<bin>",
9
+ * "version": "<bin-version>",
10
+ * "description": "<from cliConfig or package.json>",
11
+ * "mcpDefault": { "command": "<bin>", "args": ["serve", "--stdio"] },
12
+ * "prompts": [{ "name", "description", "arguments": [...] }],
13
+ * "skills": [{ "name", "description", "instructionFile" (path under _skills/), "resourceDirs": { references?, examples?, scripts?, assets? } }]
14
+ * }
15
+ */
16
+ import type { FrontmcpExecConfig } from './config';
17
+ import type { ExtractedSchema } from './cli-runtime/schema-extractor';
18
+ export interface BinMeta {
19
+ name: string;
20
+ version: string;
21
+ description: string;
22
+ mcpDefault: {
23
+ command: string;
24
+ args: string[];
25
+ };
26
+ prompts: Array<{
27
+ name: string;
28
+ description?: string;
29
+ arguments?: Array<{
30
+ name: string;
31
+ description?: string;
32
+ required?: boolean;
33
+ }>;
34
+ }>;
35
+ skills: Array<{
36
+ name: string;
37
+ description?: string;
38
+ tags?: string[];
39
+ license?: string;
40
+ instructionFile?: string;
41
+ resourceDirs?: {
42
+ references?: string;
43
+ examples?: string;
44
+ scripts?: string;
45
+ assets?: string;
46
+ };
47
+ }>;
48
+ }
49
+ export declare function writeBinMeta(outDir: string, config: FrontmcpExecConfig, schema: ExtractedSchema): Promise<void>;
@@ -0,0 +1,68 @@
1
+ "use strict";
2
+ /**
3
+ * Issue #411 — write `bin-meta.json` next to the CLI bundle. The per-bin
4
+ * `<bin> install -p claude|codex` reads this sidecar at runtime to drive
5
+ * the shared plugin-emitter without re-running schema extraction.
6
+ *
7
+ * Shape:
8
+ * {
9
+ * "name": "<bin>",
10
+ * "version": "<bin-version>",
11
+ * "description": "<from cliConfig or package.json>",
12
+ * "mcpDefault": { "command": "<bin>", "args": ["serve", "--stdio"] },
13
+ * "prompts": [{ "name", "description", "arguments": [...] }],
14
+ * "skills": [{ "name", "description", "instructionFile" (path under _skills/), "resourceDirs": { references?, examples?, scripts?, assets? } }]
15
+ * }
16
+ */
17
+ Object.defineProperty(exports, "__esModule", { value: true });
18
+ exports.writeBinMeta = writeBinMeta;
19
+ const tslib_1 = require("tslib");
20
+ const path = tslib_1.__importStar(require("path"));
21
+ const utils_1 = require("@frontmcp/utils");
22
+ async function writeBinMeta(outDir, config, schema) {
23
+ const meta = {
24
+ name: config.name,
25
+ version: config.version ?? '0.0.0',
26
+ description: config.cli?.description ?? `${config.name} (FrontMCP server)`,
27
+ mcpDefault: { command: config.name, args: ['serve', '--stdio'] },
28
+ prompts: schema.prompts.map((p) => ({
29
+ name: p.name,
30
+ description: p.description,
31
+ arguments: p.arguments,
32
+ })),
33
+ skills: schema.skillAssets.map((asset) => {
34
+ // The bin runtime expects RELATIVE paths under `_skills/` so the bundle
35
+ // is portable across install destinations.
36
+ const instructionFile = asset.instructionFile
37
+ ? path.join('_skills', `${asset.skillName}--${path.basename(asset.instructionFile)}`)
38
+ : undefined;
39
+ const resourceDirs = {};
40
+ for (const kind of ['references', 'examples', 'scripts', 'assets']) {
41
+ if (asset.resourceDirs?.[kind]) {
42
+ resourceDirs[kind] = path.join('_skills', `${asset.skillName}--${kind}`);
43
+ }
44
+ }
45
+ return {
46
+ name: asset.skillName,
47
+ description: asset.description,
48
+ tags: asset.tags && asset.tags.length > 0 ? asset.tags : undefined,
49
+ license: asset.license,
50
+ instructionFile,
51
+ resourceDirs: Object.keys(resourceDirs).length > 0 ? resourceDirs : undefined,
52
+ };
53
+ }),
54
+ };
55
+ // Drop undefined fields so the JSON payload matches the TS interface (no
56
+ // explicit "description": undefined keys land in stringified output).
57
+ const cleanSkills = meta.skills.map((s) => omitUndefined(s));
58
+ await (0, utils_1.writeJSON)(path.join(outDir, 'bin-meta.json'), { ...meta, skills: cleanSkills });
59
+ }
60
+ function omitUndefined(obj) {
61
+ const out = {};
62
+ for (const [k, v] of Object.entries(obj)) {
63
+ if (v !== undefined)
64
+ out[k] = v;
65
+ }
66
+ return out;
67
+ }
68
+ //# sourceMappingURL=bin-meta.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bin-meta.js","sourceRoot":"","sources":["../../../../../src/commands/build/exec/bin-meta.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Issue #411 — write `bin-meta.json` next to the CLI bundle. The per-bin\n * `<bin> install -p claude|codex` reads this sidecar at runtime to drive\n * the shared plugin-emitter without re-running schema extraction.\n *\n * Shape:\n * {\n * \"name\": \"<bin>\",\n * \"version\": \"<bin-version>\",\n * \"description\": \"<from cliConfig or package.json>\",\n * \"mcpDefault\": { \"command\": \"<bin>\", \"args\": [\"serve\", \"--stdio\"] },\n * \"prompts\": [{ \"name\", \"description\", \"arguments\": [...] }],\n * \"skills\": [{ \"name\", \"description\", \"instructionFile\" (path under _skills/), \"resourceDirs\": { references?, examples?, scripts?, assets? } }]\n * }\n */\n\nimport * as path from 'path';\n\nimport { writeJSON } from '@frontmcp/utils';\n\nimport type { FrontmcpExecConfig } from './config';\nimport type { ExtractedSchema } from './cli-runtime/schema-extractor';\n\nexport interface BinMeta {\n name: string;\n version: string;\n description: string;\n mcpDefault: { command: string; args: string[] };\n prompts: Array<{\n name: string;\n description?: string;\n arguments?: Array<{ name: string; description?: string; required?: boolean }>;\n }>;\n skills: Array<{\n name: string;\n description?: string;\n tags?: string[];\n license?: string;\n instructionFile?: string;\n resourceDirs?: { references?: string; examples?: string; scripts?: string; assets?: string };\n }>;\n}\n\nexport async function writeBinMeta(\n outDir: string,\n config: FrontmcpExecConfig,\n schema: ExtractedSchema,\n): Promise<void> {\n const meta: BinMeta = {\n name: config.name,\n version: config.version ?? '0.0.0',\n description: config.cli?.description ?? `${config.name} (FrontMCP server)`,\n mcpDefault: { command: config.name, args: ['serve', '--stdio'] },\n prompts: schema.prompts.map((p) => ({\n name: p.name,\n description: p.description,\n arguments: p.arguments,\n })),\n skills: schema.skillAssets.map((asset) => {\n // The bin runtime expects RELATIVE paths under `_skills/` so the bundle\n // is portable across install destinations.\n const instructionFile = asset.instructionFile\n ? path.join('_skills', `${asset.skillName}--${path.basename(asset.instructionFile)}`)\n : undefined;\n const resourceDirs: BinMeta['skills'][number]['resourceDirs'] = {};\n for (const kind of ['references', 'examples', 'scripts', 'assets'] as const) {\n if (asset.resourceDirs?.[kind]) {\n resourceDirs[kind] = path.join('_skills', `${asset.skillName}--${kind}`);\n }\n }\n return {\n name: asset.skillName,\n description: asset.description,\n tags: asset.tags && asset.tags.length > 0 ? asset.tags : undefined,\n license: asset.license,\n instructionFile,\n resourceDirs: Object.keys(resourceDirs).length > 0 ? resourceDirs : undefined,\n };\n }),\n };\n\n // Drop undefined fields so the JSON payload matches the TS interface (no\n // explicit \"description\": undefined keys land in stringified output).\n const cleanSkills = meta.skills.map((s) => omitUndefined(s));\n await writeJSON(path.join(outDir, 'bin-meta.json'), { ...meta, skills: cleanSkills });\n}\n\nfunction omitUndefined<T extends Record<string, unknown>>(obj: T): Partial<T> {\n const out: Partial<T> = {};\n for (const [k, v] of Object.entries(obj)) {\n if (v !== undefined) out[k as keyof T] = v as T[keyof T];\n }\n return out;\n}\n"]}
@@ -1362,16 +1362,158 @@ function generateInstallCommand(appName, nativeDeps, selfContained) {
1362
1362
  depEntries.push(` { name: ${JSON.stringify(pkg)}, type: 'npm', install: 'npm install ${pkg}', check: 'npm ls ${pkg}' }`);
1363
1363
  }
1364
1364
  }
1365
- return `program
1365
+ return `function _frontmcpCollectArg(value, acc) { return Array.isArray(acc) ? acc.concat(value) : [value]; }
1366
+
1367
+ program
1366
1368
  .command('install')
1367
- .description('Install to ~/.frontmcp/ and set up dependencies')
1369
+ .description('Install to ~/.frontmcp/ and set up dependencies, OR emit an IDE plugin (use -p)')
1368
1370
  .option('--prefix <path>', 'Installation prefix directory')
1369
1371
  .option('--bin-dir <path>', 'Directory for symlink (default: ~/.local/bin or /usr/local/bin)')
1372
+ .option('-p, --provider <provider>', 'Emit plugin for provider: claude | codex (repeatable)', _frontmcpCollectArg, [])
1373
+ .option('--scope <scope>', 'Plugin scope when -p is set: project | user', 'project')
1374
+ .option('--no-skills', 'Skip the skills/ subtree (when -p claude)')
1375
+ .option('--no-commands', 'Skip the commands/ subtree (when -p claude)')
1376
+ .option('--only-mcp', 'Skip plugin folder; just register the MCP server')
1377
+ .option('--command <cmd>', 'Override MCP server invocation in the plugin manifest')
1378
+ .option('--env <name>', 'Add env-var placeholder to plugin manifest (repeatable)', _frontmcpCollectArg, [])
1379
+ .option('--dir <dir>', 'Override plugin destination root')
1380
+ .option('--dry-run', 'Print plan; do not write')
1381
+ .option('--status', 'Print install status per provider; exit 0')
1370
1382
  .action(async function(opts) {
1371
1383
  var fs = require('fs');
1372
1384
  var pathMod = require('path');
1373
1385
  var os = require('os');
1374
1386
  var exec = require('child_process').execSync;
1387
+
1388
+ // Issue #411 — when -p is set OR --status is set, run the plugin-install
1389
+ // path instead of the legacy bundle-copy + symlink behavior.
1390
+ var providers = Array.isArray(opts.provider) ? opts.provider : [];
1391
+ if (opts.status || providers.length > 0) {
1392
+ var emitter = require('./plugin-emitter');
1393
+ var binMetaPath = pathMod.join(SCRIPT_DIR, 'bin-meta.json');
1394
+ var meta;
1395
+ try { meta = JSON.parse(fs.readFileSync(binMetaPath, 'utf8')); }
1396
+ catch (e) {
1397
+ console.error('Could not read bin-meta.json at ' + binMetaPath + '. Was the bin built with a recent frontmcp?');
1398
+ process.exit(1);
1399
+ }
1400
+ var pkgJsonPath = pathMod.join(__dirname, '..', '..', 'package.json');
1401
+ var cliVersion = '0.0.0';
1402
+ try { cliVersion = (require(pkgJsonPath) || {}).version || '0.0.0'; } catch (e) { /* ok */ }
1403
+
1404
+ function resolveDestRoot() {
1405
+ if (opts.dir) return pathMod.resolve(opts.dir);
1406
+ if (opts.scope === 'user') return pathMod.join(os.homedir(), '.claude', 'plugins');
1407
+ return pathMod.join(process.cwd(), '.claude', 'plugins');
1408
+ }
1409
+
1410
+ if (opts.status) {
1411
+ console.log(meta.name + ' install --status');
1412
+ var destRootSt = resolveDestRoot();
1413
+ var pluginDirSt = pathMod.join(destRootSt, meta.name);
1414
+ var installed = await emitter.readInstalledPluginVersion(pluginDirSt);
1415
+ if (installed) {
1416
+ var tag = installed === meta.version ? 'installed' : 'outdated';
1417
+ console.log(' claude: ' + tag + ' v' + installed + (tag === 'outdated' ? ' (bin at v' + meta.version + ')' : '') + ' at ' + pluginDirSt);
1418
+ } else {
1419
+ console.log(' claude: not installed at ' + pluginDirSt);
1420
+ }
1421
+ var codexConfigSt = pathMod.join(os.homedir(), '.codex', 'config.toml');
1422
+ if (fs.existsSync(codexConfigSt) && fs.readFileSync(codexConfigSt, 'utf8').indexOf('# frontmcp:codex-start:' + meta.name) !== -1) {
1423
+ console.log(' codex: installed entry for ' + meta.name + ' in ' + codexConfigSt);
1424
+ } else {
1425
+ console.log(' codex: not installed in ' + codexConfigSt);
1426
+ }
1427
+ return;
1428
+ }
1429
+
1430
+ function buildSkills() {
1431
+ if (opts.skills === false || opts.onlyMcp) return [];
1432
+ var out = [];
1433
+ for (var i = 0; i < (meta.skills || []).length; i++) {
1434
+ var s = meta.skills[i];
1435
+ var resourceDirs = {};
1436
+ if (s.resourceDirs) {
1437
+ for (var k in s.resourceDirs) {
1438
+ if (Object.prototype.hasOwnProperty.call(s.resourceDirs, k)) {
1439
+ resourceDirs[k] = pathMod.join(SCRIPT_DIR, s.resourceDirs[k]);
1440
+ }
1441
+ }
1442
+ }
1443
+ out.push({
1444
+ name: s.name,
1445
+ description: s.description || (s.name + ' skill from ' + meta.name),
1446
+ tags: Array.isArray(s.tags) && s.tags.length > 0 ? s.tags : undefined,
1447
+ license: s.license || undefined,
1448
+ instructionFile: s.instructionFile ? pathMod.join(SCRIPT_DIR, s.instructionFile) : undefined,
1449
+ resourceDirs: Object.keys(resourceDirs).length > 0 ? resourceDirs : undefined,
1450
+ });
1451
+ }
1452
+ return out;
1453
+ }
1454
+
1455
+ function buildCommands() {
1456
+ if (opts.commands === false || opts.onlyMcp) return [];
1457
+ return (meta.prompts || []).map(function(p) {
1458
+ return { name: p.name, description: p.description, arguments: p.arguments };
1459
+ });
1460
+ }
1461
+
1462
+ for (var pi = 0; pi < providers.length; pi++) {
1463
+ var provider = providers[pi];
1464
+ if (provider === 'claude') {
1465
+ var destRoot = resolveDestRoot();
1466
+ var result = await emitter.emitClaudePlugin({
1467
+ destRoot: destRoot,
1468
+ name: meta.name,
1469
+ version: meta.version,
1470
+ description: meta.description,
1471
+ mcpCommand: opts.command || meta.mcpDefault.command,
1472
+ mcpArgs: meta.mcpDefault.args,
1473
+ envHints: Array.isArray(opts.env) ? opts.env : [],
1474
+ skills: buildSkills(),
1475
+ commands: buildCommands(),
1476
+ cliVersion: cliVersion,
1477
+ dryRun: opts.dryRun,
1478
+ });
1479
+ if (opts.dryRun) {
1480
+ console.log('[install:claude] dry-run plan');
1481
+ console.log(' pluginDir: ' + result.pluginDir);
1482
+ console.log(' filesWritten (planned):');
1483
+ for (var fwi = 0; fwi < result.filesWritten.length; fwi++) console.log(' + ' + result.filesWritten[fwi]);
1484
+ } else {
1485
+ console.log('✓ Wrote ' + result.pluginDir + '/ (' + (result.manifest.skills || []).length + ' skills, ' + ((result.manifest.commands || []).length) + ' commands, 1 MCP server)');
1486
+ console.log(' Restart Claude Code (or run /plugins reload) to pick up the plugin.');
1487
+ }
1488
+ } else if (provider === 'codex') {
1489
+ var codexConfig = pathMod.join(os.homedir(), '.codex', 'config.toml');
1490
+ var env = {};
1491
+ var envList = Array.isArray(opts.env) ? opts.env : [];
1492
+ for (var ei = 0; ei < envList.length; ei++) env[envList[ei]] = '${'$'}{' + envList[ei] + '}';
1493
+ var codexResult = await emitter.emitCodexEntry({
1494
+ configPath: codexConfig,
1495
+ name: meta.name,
1496
+ command: opts.command || meta.mcpDefault.command,
1497
+ args: meta.mcpDefault.args,
1498
+ env: env,
1499
+ dryRun: opts.dryRun,
1500
+ });
1501
+ if (opts.dryRun) {
1502
+ console.log('[install:codex] dry-run plan');
1503
+ console.log(' configPath: ' + codexConfig);
1504
+ console.log(codexResult.configContent);
1505
+ } else {
1506
+ console.log('✓ Updated ' + codexConfig + ' with [[mcp_servers]] entry for ' + meta.name);
1507
+ }
1508
+ } else {
1509
+ console.error('Unknown provider: ' + provider);
1510
+ process.exitCode = 1;
1511
+ return;
1512
+ }
1513
+ }
1514
+ return;
1515
+ }
1516
+
1375
1517
  var installBase = opts.prefix || FRONTMCP_HOME;
1376
1518
  var appDir = pathMod.join(installBase, 'apps', ${JSON.stringify(appName)});
1377
1519
  var dirs = ['', '/data', '/sessions', '/credentials'].map(function(s) { return appDir + s; });
@@ -1432,13 +1574,63 @@ ${depEntries.join(',\n')}
1432
1574
 
1433
1575
  program
1434
1576
  .command('uninstall')
1435
- .description('Remove from ~/.frontmcp/ and clean up')
1577
+ .description('Remove from ~/.frontmcp/, OR remove an IDE plugin (use -p)')
1436
1578
  .option('--prefix <path>', 'Installation prefix directory')
1437
1579
  .option('--bin-dir <path>', 'Directory where symlink was created')
1580
+ .option('-p, --provider <provider>', 'Remove plugin for provider: claude | codex (repeatable)', _frontmcpCollectArg, [])
1581
+ .option('--scope <scope>', 'Plugin scope when -p is set: project | user', 'project')
1582
+ .option('--dir <dir>', 'Override plugin destination root')
1438
1583
  .action(async function(opts) {
1439
1584
  var fs = require('fs');
1440
1585
  var pathMod = require('path');
1441
1586
  var os = require('os');
1587
+
1588
+ // Issue #411 — when -p is set, route through the shared plugin-emitter
1589
+ // to remove the IDE plugin instead of the legacy ~/.frontmcp uninstall.
1590
+ var providers = Array.isArray(opts.provider) ? opts.provider : [];
1591
+ if (providers.length > 0) {
1592
+ var emitter = require('./plugin-emitter');
1593
+ var binMetaPath = pathMod.join(SCRIPT_DIR, 'bin-meta.json');
1594
+ var meta;
1595
+ try { meta = JSON.parse(fs.readFileSync(binMetaPath, 'utf8')); }
1596
+ catch (e) {
1597
+ console.error('Could not read bin-meta.json at ' + binMetaPath + '. Was the bin built with a recent frontmcp?');
1598
+ process.exit(1);
1599
+ }
1600
+
1601
+ function resolveDestRoot() {
1602
+ if (opts.dir) return pathMod.resolve(opts.dir);
1603
+ if (opts.scope === 'user') return pathMod.join(os.homedir(), '.claude', 'plugins');
1604
+ return pathMod.join(process.cwd(), '.claude', 'plugins');
1605
+ }
1606
+
1607
+ for (var pi = 0; pi < providers.length; pi++) {
1608
+ var provider = providers[pi];
1609
+ if (provider === 'claude') {
1610
+ var destRoot = resolveDestRoot();
1611
+ var result = await emitter.removeClaudePlugin({ destRoot: destRoot, name: meta.name });
1612
+ if (result.removed.length === 0) {
1613
+ console.log(' claude: nothing to remove at ' + result.pluginDir);
1614
+ } else {
1615
+ console.log('✓ Removed ' + result.removed.length + ' file(s) from ' + result.pluginDir);
1616
+ }
1617
+ } else if (provider === 'codex') {
1618
+ var codexConfig = pathMod.join(os.homedir(), '.codex', 'config.toml');
1619
+ var codexResult = await emitter.removeCodexEntry({ configPath: codexConfig, name: meta.name });
1620
+ if (codexResult.removed) {
1621
+ console.log('✓ Removed [[mcp_servers]] entry for ' + meta.name + ' from ' + codexConfig);
1622
+ } else {
1623
+ console.log(' codex: no entry for ' + meta.name + ' in ' + codexConfig);
1624
+ }
1625
+ } else {
1626
+ console.error('Unknown provider: ' + provider);
1627
+ process.exitCode = 1;
1628
+ return;
1629
+ }
1630
+ }
1631
+ return;
1632
+ }
1633
+
1442
1634
  var uninstallBase = opts.prefix || FRONTMCP_HOME;
1443
1635
  var appDir = pathMod.join(uninstallBase, 'apps', ${JSON.stringify(appName)});
1444
1636