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.
- package/README.md +38 -29
- package/package.json +4 -4
- package/src/commands/build/exec/bin-meta.d.ts +49 -0
- package/src/commands/build/exec/bin-meta.js +68 -0
- package/src/commands/build/exec/bin-meta.js.map +1 -0
- package/src/commands/build/exec/cli-runtime/generate-cli-entry.js +195 -3
- package/src/commands/build/exec/cli-runtime/generate-cli-entry.js.map +1 -1
- package/src/commands/build/exec/cli-runtime/plugin-emitter.d.ts +160 -0
- package/src/commands/build/exec/cli-runtime/plugin-emitter.js +512 -0
- package/src/commands/build/exec/cli-runtime/plugin-emitter.js.map +1 -0
- package/src/commands/build/exec/cli-runtime/schema-extractor.d.ts +13 -1
- package/src/commands/build/exec/cli-runtime/schema-extractor.js +29 -3
- package/src/commands/build/exec/cli-runtime/schema-extractor.js.map +1 -1
- package/src/commands/build/exec/cli-runtime/skill-md-compose.d.ts +25 -0
- package/src/commands/build/exec/cli-runtime/skill-md-compose.js +63 -0
- package/src/commands/build/exec/cli-runtime/skill-md-compose.js.map +1 -0
- package/src/commands/build/exec/index.js +26 -0
- package/src/commands/build/exec/index.js.map +1 -1
- package/src/commands/build/exec/runner-script.js +16 -4
- package/src/commands/build/exec/runner-script.js.map +1 -1
- package/src/commands/dev/bridge/child-supervisor.d.ts +48 -0
- package/src/commands/dev/bridge/child-supervisor.js +228 -0
- package/src/commands/dev/bridge/child-supervisor.js.map +1 -0
- package/src/commands/dev/bridge/errors.d.ts +23 -0
- package/src/commands/dev/bridge/errors.js +34 -0
- package/src/commands/dev/bridge/errors.js.map +1 -0
- package/src/commands/dev/bridge/index.d.ts +30 -0
- package/src/commands/dev/bridge/index.js +220 -0
- package/src/commands/dev/bridge/index.js.map +1 -0
- package/src/commands/dev/bridge/log.d.ts +29 -0
- package/src/commands/dev/bridge/log.js +82 -0
- package/src/commands/dev/bridge/log.js.map +1 -0
- package/src/commands/dev/bridge/state-machine.d.ts +56 -0
- package/src/commands/dev/bridge/state-machine.js +245 -0
- package/src/commands/dev/bridge/state-machine.js.map +1 -0
- package/src/commands/dev/bridge/stdio-framer.d.ts +47 -0
- package/src/commands/dev/bridge/stdio-framer.js +128 -0
- package/src/commands/dev/bridge/stdio-framer.js.map +1 -0
- package/src/commands/dev/bridge/upstream-client.d.ts +49 -0
- package/src/commands/dev/bridge/upstream-client.js +159 -0
- package/src/commands/dev/bridge/upstream-client.js.map +1 -0
- package/src/commands/dev/bridge/watcher.d.ts +30 -0
- package/src/commands/dev/bridge/watcher.js +87 -0
- package/src/commands/dev/bridge/watcher.js.map +1 -0
- package/src/commands/dev/dev.d.ts +34 -1
- package/src/commands/dev/dev.js +168 -14
- package/src/commands/dev/dev.js.map +1 -1
- package/src/commands/dev/inspector.d.ts +13 -1
- package/src/commands/dev/inspector.js +77 -3
- package/src/commands/dev/inspector.js.map +1 -1
- package/src/commands/dev/port.d.ts +23 -0
- package/src/commands/dev/port.js +87 -0
- package/src/commands/dev/port.js.map +1 -0
- package/src/commands/dev/register.d.ts +1 -1
- package/src/commands/dev/register.js +28 -4
- package/src/commands/dev/register.js.map +1 -1
- package/src/commands/dev/test.d.ts +26 -1
- package/src/commands/dev/test.js +181 -64
- package/src/commands/dev/test.js.map +1 -1
- package/src/commands/eject/mcp-client.d.ts +25 -0
- package/src/commands/eject/mcp-client.js +74 -0
- package/src/commands/eject/mcp-client.js.map +1 -0
- package/src/commands/eject/register.d.ts +9 -0
- package/src/commands/eject/register.js +56 -0
- package/src/commands/eject/register.js.map +1 -0
- package/src/commands/install/install-claude-plugin.d.ts +13 -0
- package/src/commands/install/install-claude-plugin.js +327 -0
- package/src/commands/install/install-claude-plugin.js.map +1 -0
- package/src/commands/install/register.d.ts +16 -0
- package/src/commands/install/register.js +70 -0
- package/src/commands/install/register.js.map +1 -0
- package/src/commands/scaffold/create.js +52 -8
- package/src/commands/scaffold/create.js.map +1 -1
- package/src/commands/skills/from-entry.d.ts +31 -0
- package/src/commands/skills/from-entry.js +68 -0
- package/src/commands/skills/from-entry.js.map +1 -0
- package/src/commands/skills/install.d.ts +12 -0
- package/src/commands/skills/install.js +173 -8
- package/src/commands/skills/install.js.map +1 -1
- package/src/commands/skills/register.js +7 -3
- package/src/commands/skills/register.js.map +1 -1
- package/src/config/frontmcp-config.loader.d.ts +28 -0
- package/src/config/frontmcp-config.loader.js +146 -67
- package/src/config/frontmcp-config.loader.js.map +1 -1
- package/src/config/frontmcp-config.resolve.d.ts +67 -0
- package/src/config/frontmcp-config.resolve.js +118 -0
- package/src/config/frontmcp-config.resolve.js.map +1 -0
- package/src/config/frontmcp-config.schema.d.ts +207 -0
- package/src/config/frontmcp-config.schema.js +217 -1
- package/src/config/frontmcp-config.schema.js.map +1 -1
- package/src/config/frontmcp-config.types.d.ts +133 -0
- package/src/config/frontmcp-config.types.js.map +1 -1
- package/src/config/index.d.ts +2 -1
- package/src/config/index.js +3 -1
- package/src/config/index.js.map +1 -1
- package/src/core/args.d.ts +13 -0
- package/src/core/args.js.map +1 -1
- package/src/core/bridge.js +39 -0
- package/src/core/bridge.js.map +1 -1
- package/src/core/cli.d.ts +0 -6
- package/src/core/cli.js +23 -3
- package/src/core/cli.js.map +1 -1
- package/src/core/help.d.ts +1 -1
- package/src/core/help.js +27 -6
- package/src/core/help.js.map +1 -1
- package/src/core/program.d.ts +1 -1
- package/src/core/program.js +56 -12
- package/src/core/program.js.map +1 -1
- package/src/core/project-commands.d.ts +44 -0
- package/src/core/project-commands.js +216 -0
- package/src/core/project-commands.js.map +1 -0
- package/src/core/tsconfig.d.ts +20 -0
- package/src/core/tsconfig.js +41 -2
- 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
|
|
10
|
+
**The production-grade, TypeScript-first framework for building MCP servers — decorators, DI, auth, and Streamable HTTP, batteries included.**
|
|
11
11
|
|
|
12
12
|
[](https://www.npmjs.com/package/@frontmcp/sdk)
|
|
13
|
-
[](https://nodejs.org)
|
|
14
14
|
[](https://github.com/agentfront/frontmcp/blob/main/LICENSE)
|
|
15
15
|
[](https://snyk.io/test/github/agentfront/frontmcp)
|
|
16
16
|
|
|
@@ -20,12 +20,17 @@
|
|
|
20
20
|
|
|
21
21
|
---
|
|
22
22
|
|
|
23
|
-
FrontMCP
|
|
24
|
-
|
|
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] · [Quickstart][docs-quickstart]
|
|
54
67
|
|
|
55
68
|
## Capabilities
|
|
56
69
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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.
|
|
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.
|
|
35
|
-
"@frontmcp/utils": "1.
|
|
36
|
-
"@frontmcp/skills": "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":";AAAA;;;;;;;;;;;;;;GAcG;;AA6BH,oCA0CC;;AArED,mDAA6B;AAE7B,2CAA4C;AAyBrC,KAAK,UAAU,YAAY,CAChC,MAAc,EACd,MAA0B,EAC1B,MAAuB;IAEvB,MAAM,IAAI,GAAY;QACpB,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,OAAO,EAAE,MAAM,CAAC,OAAO,IAAI,OAAO;QAClC,WAAW,EAAE,MAAM,CAAC,GAAG,EAAE,WAAW,IAAI,GAAG,MAAM,CAAC,IAAI,oBAAoB;QAC1E,UAAU,EAAE,EAAE,OAAO,EAAE,MAAM,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,OAAO,EAAE,SAAS,CAAC,EAAE;QAChE,OAAO,EAAE,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YAClC,IAAI,EAAE,CAAC,CAAC,IAAI;YACZ,WAAW,EAAE,CAAC,CAAC,WAAW;YAC1B,SAAS,EAAE,CAAC,CAAC,SAAS;SACvB,CAAC,CAAC;QACH,MAAM,EAAE,MAAM,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE;YACvC,wEAAwE;YACxE,2CAA2C;YAC3C,MAAM,eAAe,GAAG,KAAK,CAAC,eAAe;gBAC3C,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,GAAG,KAAK,CAAC,SAAS,KAAK,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,eAAe,CAAC,EAAE,CAAC;gBACrF,CAAC,CAAC,SAAS,CAAC;YACd,MAAM,YAAY,GAA8C,EAAE,CAAC;YACnE,KAAK,MAAM,IAAI,IAAI,CAAC,YAAY,EAAE,UAAU,EAAE,SAAS,EAAE,QAAQ,CAAU,EAAE,CAAC;gBAC5E,IAAI,KAAK,CAAC,YAAY,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;oBAC/B,YAAY,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,GAAG,KAAK,CAAC,SAAS,KAAK,IAAI,EAAE,CAAC,CAAC;gBAC3E,CAAC;YACH,CAAC;YACD,OAAO;gBACL,IAAI,EAAE,KAAK,CAAC,SAAS;gBACrB,WAAW,EAAE,KAAK,CAAC,WAAW;gBAC9B,IAAI,EAAE,KAAK,CAAC,IAAI,IAAI,KAAK,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS;gBAClE,OAAO,EAAE,KAAK,CAAC,OAAO;gBACtB,eAAe;gBACf,YAAY,EAAE,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,SAAS;aAC9E,CAAC;QACJ,CAAC,CAAC;KACH,CAAC;IAEF,yEAAyE;IACzE,sEAAsE;IACtE,MAAM,WAAW,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC;IAC7D,MAAM,IAAA,iBAAS,EAAC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,eAAe,CAAC,EAAE,EAAE,GAAG,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC,CAAC;AACxF,CAAC;AAED,SAAS,aAAa,CAAoC,GAAM;IAC9D,MAAM,GAAG,GAAe,EAAE,CAAC;IAC3B,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;QACzC,IAAI,CAAC,KAAK,SAAS;YAAE,GAAG,CAAC,CAAY,CAAC,GAAG,CAAe,CAAC;IAC3D,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC","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 `
|
|
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
|
|
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
|
|