Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
7ac7eae
feat: add SEA build support with reinitialization handling for transport
frontegg-david Mar 25, 2026
53defbe
feat: improve file existence checks and enhance transport session han…
frontegg-david Mar 25, 2026
aea37dc
feat: unify build command by replacing --exec and --adapter flags wit…
frontegg-david Mar 25, 2026
17416ce
feat: reset pending initialization state in web transport
frontegg-david Mar 25, 2026
80259f2
feat: replace --exec and --adapter flags with --target option in CLI …
frontegg-david Mar 25, 2026
0636663
feat: update CLI build commands to use --js flag with --target option
frontegg-david Mar 25, 2026
7564010
feat: enhance CLI error handling with unknown command feedback
frontegg-david Mar 25, 2026
8dd875e
feat: enhance CLI error handling with unknown command feedback
frontegg-david Mar 25, 2026
0ea8d09
feat: enhance CLI error handling with unknown command feedback
frontegg-david Mar 25, 2026
23daf10
Merge branch 'release/1.0.x' into improve-streambale-http
frontegg-david Mar 26, 2026
db6165a
refactor: update type definitions and improve scope handling across m…
frontegg-david Mar 26, 2026
23c9906
feat: add skill directory generator and CLI commands for skills manag…
frontegg-david Mar 26, 2026
5c29be5
refactor: remove unused imports and improve error handling for transp…
frontegg-david Mar 27, 2026
271f606
refactor: enhance error handling by introducing ElicitationStoreNotIn…
frontegg-david Mar 27, 2026
46a6bb1
Merge branch 'refactor-code' into add-skills
frontegg-david Mar 27, 2026
224fc2d
Merge branch 'release/1.0.x' into add-skills
frontegg-david Mar 27, 2026
2406f13
refactor: replace fs with utility functions for file operations and i…
frontegg-david Mar 27, 2026
0b56350
refactor: add ESLint configuration for skills-validation.spec.ts to h…
frontegg-david Mar 27, 2026
a37fd2e
refactor: add ESLint configuration for skills-validation.spec.ts to h…
frontegg-david Mar 27, 2026
36abe4a
refactor: update TEMPLATE.md and SKILL.md for improved skill document…
frontegg-david Mar 27, 2026
98051a3
feat: add setup references for SQLite and Redis, update README structure
frontegg-david Mar 27, 2026
dae43dd
feat: add setup references for SQLite and Redis, update README structure
frontegg-david Mar 27, 2026
35793df
feat: enhance SKILL.md and related documentation with prerequisites a…
frontegg-david Mar 27, 2026
1d43424
refactor: improve CLI tests and documentation for clarity and accuracy
frontegg-david Mar 27, 2026
6ac46c4
refactor: enhance CLI test assertions and improve documentation clarity
frontegg-david Mar 27, 2026
4968ab2
feat: add official adapters and plugins to SKILL.md for enhanced inte…
frontegg-david Mar 27, 2026
f4c71e9
feat: add official adapters and plugins to SKILL.md for enhanced inte…
frontegg-david Mar 27, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ Located in `/libs/*`:
- **sdk** (`libs/sdk`) - Core FrontMCP SDK
- **adapters** (`libs/adapters`) - Framework adapters and integrations
- **plugins** (`libs/plugins`) - Plugin system and extensions
- **skills** (`libs/skills`) - Curated SKILL.md catalog for scaffold and install tooling

> **Note:** `ast-guard`, `vectoriadb`, `enclave-vm`, `json-schema-to-zod-v3`, and `mcp-from-openapi` have been moved to external repositories.

Expand Down Expand Up @@ -83,6 +84,15 @@ export * from './errors';
- **Scope**: Standalone auth library used by SDK and other packages
- **Note**: All authentication-related code should be placed in this library, not in SDK

#### @frontmcp/skills

- **Purpose**: Curated SKILL.md catalog for scaffolding and future skill installation
- **Scope**: Publishable catalog of markdown-based skills organized by category and deployment target
- **Structure**: `catalog/` contains SKILL.md directories; `src/` has manifest types and loader helpers
- **Build**: Custom asset-aware build that copies `catalog/**` into dist (not stock Nx lib generator)
- **Manifest**: `catalog/skills-manifest.json` is the single source of truth for scaffold filtering and future installer
- **Adding skills**: Create dir in `catalog/<category>/<name>/`, add `SKILL.md`, update manifest, run `nx test skills`

### Demo Applications

#### demo (`apps/demo`)
Expand Down
169 changes: 169 additions & 0 deletions apps/e2e/demo-e2e-cli-exec/e2e/cli-skills.e2e.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
/**
* E2E tests for `frontmcp skills` CLI commands: list, search, install.
*
* Runs the actual frontmcp CLI binary against the real @frontmcp/skills catalog.
*/

import * as fs from 'node:fs';
import * as os from 'node:os';
import * as path from 'node:path';
import { runFrontmcpCli } from './helpers/exec-cli';

describe('CLI Skills Commands', () => {
// ─── skills list ────────────────────────────────────────────────────────────

describe('skills list', () => {
it('should list all skills with exit code 0', () => {
const { stdout, exitCode } = runFrontmcpCli(['skills', 'list']);
expect(exitCode).toBe(0);
expect(stdout).toContain('Skills Catalog');
});

it('should include known skill names', () => {
const { stdout } = runFrontmcpCli(['skills', 'list']);
expect(stdout).toContain('frontmcp-setup');
expect(stdout).toContain('frontmcp-deployment');
expect(stdout).toContain('frontmcp-development');
});

it('should filter by category', () => {
const { stdout, exitCode } = runFrontmcpCli(['skills', 'list', '--category', 'setup']);
expect(exitCode).toBe(0);
expect(stdout).toContain('frontmcp-setup');
// Should NOT include deployment skills
expect(stdout).not.toContain('frontmcp-deployment');
expect(stdout).not.toContain('frontmcp-development');
});

it('should filter by tag', () => {
const { stdout, exitCode } = runFrontmcpCli(['skills', 'list', '--tag', 'redis']);
expect(exitCode).toBe(0);
expect(stdout).toContain('frontmcp-setup');
});

it('should filter by bundle', () => {
const { stdout, exitCode } = runFrontmcpCli(['skills', 'list', '--bundle', 'minimal']);
expect(exitCode).toBe(0);
expect(stdout).toContain('frontmcp-setup');
// Skills not in minimal bundle should be excluded
expect(stdout).not.toContain('frontmcp-guides');
});
});

// ─── skills search ──────────────────────────────────────────────────────────

describe('skills search', () => {
it('should return results for a keyword query', () => {
const { stdout, exitCode } = runFrontmcpCli(['skills', 'search', 'redis']);
expect(exitCode).toBe(0);
expect(stdout).toContain('frontmcp-config');
expect(stdout).toContain('result(s)');
});

it('should return results for a multi-word query', () => {
const { stdout, exitCode } = runFrontmcpCli(['skills', 'search', 'deploy serverless']);
expect(exitCode).toBe(0);
// Should match the deployment router
expect(stdout).toContain('frontmcp-deployment');
});

it('should respect --limit option', () => {
const { stdout, exitCode } = runFrontmcpCli(['skills', 'search', 'setup', '--limit', '2']);
expect(exitCode).toBe(0);
// Count result entries (lines with score: pattern)
const resultLines = stdout.split('\n').filter((line) => line.includes('score:'));
expect(resultLines.length).toBeLessThanOrEqual(2);
});

it('should respect --category filter', () => {
const { stdout, exitCode } = runFrontmcpCli(['skills', 'search', 'configure', '--category', 'config']);
expect(exitCode).toBe(0);
// Results should only be from config category
if (stdout.includes('result(s)')) {
expect(stdout).toContain('[config]');
expect(stdout).not.toContain('[setup]');
}
});

it('should show no-results message for nonsense query', () => {
const { stdout, exitCode } = runFrontmcpCli(['skills', 'search', 'xyznonexistent123']);
expect(exitCode).toBe(0);
expect(stdout).toContain('No skills found');
});
});

// ─── skills install ─────────────────────────────────────────────────────────

describe('skills install', () => {
let tmpDir: string;

beforeEach(() => {
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'frontmcp-skills-e2e-'));
});

afterEach(() => {
fs.rmSync(tmpDir, { recursive: true, force: true });
});

it('should install a skill to a custom directory', () => {
const { stdout, exitCode } = runFrontmcpCli(['skills', 'install', 'frontmcp-setup', '--dir', tmpDir]);
expect(exitCode).toBe(0);
expect(stdout).toContain('Installed');
expect(stdout).toContain('frontmcp-setup');

// Verify SKILL.md was copied
const skillMd = path.join(tmpDir, 'frontmcp-setup', 'SKILL.md');
expect(fs.existsSync(skillMd)).toBe(true);

// Verify content is non-empty
const content = fs.readFileSync(skillMd, 'utf-8');
expect(content.length).toBeGreaterThan(100);
expect(content).toContain('frontmcp-setup');
});

it('should install a skill that has resources', () => {
const { stdout, exitCode } = runFrontmcpCli(['skills', 'install', 'frontmcp-deployment', '--dir', tmpDir]);
expect(exitCode).toBe(0);
expect(stdout).toContain('Installed');

// frontmcp-deployment has hasResources: true — verify references/ was copied
const skillDir = path.join(tmpDir, 'frontmcp-deployment');
expect(fs.existsSync(path.join(skillDir, 'SKILL.md'))).toBe(true);
const refDir = path.join(skillDir, 'references');
expect(fs.existsSync(refDir)).toBe(true);
});

it('should error on unknown skill name', () => {
const { stdout, stderr, exitCode } = runFrontmcpCli([
'skills',
'install',
'nonexistent-skill-xyz',
'--dir',
tmpDir,
]);
expect(exitCode).not.toBe(0);
const output = stdout + stderr;
expect(output.toLowerCase()).toContain('not found');
});

it('should install to directory specified by --dir', () => {
const baseDir = path.join(tmpDir, 'project');
fs.mkdirSync(baseDir, { recursive: true });

const { exitCode } = runFrontmcpCli([
'skills',
'install',
'frontmcp-setup',
'--provider',
'claude',
'--dir',
baseDir,
]);
expect(exitCode).toBe(0);

// Should exist under the base dir
const skillMd = path.join(baseDir, 'frontmcp-setup', 'SKILL.md');
expect(fs.existsSync(skillMd)).toBe(true);
});
});
});
29 changes: 29 additions & 0 deletions apps/e2e/demo-e2e-cli-exec/e2e/helpers/exec-cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,35 @@ export function runCli(args: string[], extraEnv?: Record<string, string>): CliRe
}
}

// ─── FrontMCP CLI Helpers ─────────────────────────────────────────────────────

const ROOT_DIR = path.resolve(__dirname, '../../../../..');
const FRONTMCP_BIN = path.join(ROOT_DIR, 'libs', 'cli', 'dist', 'src', 'core', 'cli.js');

/**
* Run the frontmcp CLI binary directly (not a bundled demo app).
* Used for testing CLI-level commands like `skills search`, `skills list`, etc.
* Resolves @frontmcp/skills via monorepo workspace symlinks.
*/
export function runFrontmcpCli(args: string[], extraEnv?: Record<string, string>): CliResult {
try {
const stdout = execFileSync('node', [FRONTMCP_BIN, ...args], {
cwd: ROOT_DIR,
timeout: 30000,
encoding: 'utf-8',
env: { ...process.env, NODE_ENV: 'test', ...extraEnv },
});
return { stdout: stdout.toString(), stderr: '', exitCode: 0 };
} catch (err: unknown) {
const error = err as { stdout?: string | Buffer; stderr?: string | Buffer; status?: number };
return {
stdout: (error.stdout || '').toString(),
stderr: (error.stderr || '').toString(),
exitCode: error.status ?? 1,
};
}
}

/**
* Spawn a long-running server process (CLI serve or server bundle).
* Returns the ChildProcess for manual lifecycle management.
Expand Down
1 change: 1 addition & 0 deletions libs/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"@frontmcp/utils": "1.0.0-beta.8",
"commander": "^13.0.0",
"tslib": "^2.3.0",
"vectoriadb": "^2.1.3",
"@rspack/core": "^1.7.6",
"esbuild": "^0.27.3"
},
Expand Down
Loading
Loading