Big News: Socket raises $60M Series C at a $1B valuation to secure software supply chains for AI-driven development.Announcement
Sign In

@skilljack/mcp

Package Overview
Dependencies
Maintainers
1
Versions
11
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@skilljack/mcp - npm Package Compare versions

Comparing version
0.9.0
to
0.10.0
+6
-21
dist/skill-discovery.d.ts

@@ -37,3 +37,2 @@ /**

userInvocable?: boolean;
metadata?: Record<string, string>;
effectiveAssistantInvocable: boolean;

@@ -51,19 +50,2 @@ effectiveUserInvocable: boolean;

/**
* Validate a metadata key against MCP spec _meta key naming rules.
*
* Valid keys have two segments: an optional prefix and a name.
* - Prefix: labels separated by dots, followed by `/`. Labels start with a letter,
* end with letter/digit, interior can be letters/digits/hyphens.
* Implementations SHOULD use reverse DNS notation (e.g., `com.example/`).
* - Name: starts/ends with alphanumeric, interior can be alphanumerics/hyphens/underscores/dots.
* May be empty if prefix is present.
* - Reserved: prefixes where the second label is `modelcontextprotocol` or `mcp`
* (e.g., `io.modelcontextprotocol/`, `dev.mcp/`). Note: `com.example.mcp/` is NOT reserved.
*/
export declare function validateMetaKey(key: string): {
valid: boolean;
reserved?: boolean;
reason?: string;
};
/**
* Discover all skills in a directory.

@@ -113,3 +95,3 @@ * Scans for subdirectories containing SKILL.md files.

/**
* Compute MCP resource annotations for a skill.
* Compute MCP resource annotations and file size for a skill.
* Derives audience from effective invocation flags and sets default priority.

@@ -119,7 +101,10 @@ *

* @param priority - Priority hint (0.0 = least important, 1.0 = most important, default 0.5)
* @returns Annotations object for use on MCP resources
* @returns Object with annotations and optional size (in bytes) for use on MCP resources
*/
export declare function getResourceAnnotations(skill: SkillMetadata, priority?: number): Annotations;
export declare function getResourceAnnotations(skill: SkillMetadata, priority?: number): {
annotations: Annotations;
size?: number;
};
export declare const SKILL_COUNT_WARNING_THRESHOLD = 50;
export declare function warnLargeSkillCount(skillCount: number): void;
export {};

@@ -70,56 +70,2 @@ /**

/**
* Regex patterns for MCP _meta key validation.
* See: https://modelcontextprotocol.io/specification/2025-11-25/basic/index#_meta
*/
const LABEL_PATTERN = /^[a-zA-Z](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?$/;
const NAME_PATTERN = /^[a-zA-Z0-9](?:[a-zA-Z0-9._-]*[a-zA-Z0-9])?$/;
/**
* Validate a metadata key against MCP spec _meta key naming rules.
*
* Valid keys have two segments: an optional prefix and a name.
* - Prefix: labels separated by dots, followed by `/`. Labels start with a letter,
* end with letter/digit, interior can be letters/digits/hyphens.
* Implementations SHOULD use reverse DNS notation (e.g., `com.example/`).
* - Name: starts/ends with alphanumeric, interior can be alphanumerics/hyphens/underscores/dots.
* May be empty if prefix is present.
* - Reserved: prefixes where the second label is `modelcontextprotocol` or `mcp`
* (e.g., `io.modelcontextprotocol/`, `dev.mcp/`). Note: `com.example.mcp/` is NOT reserved.
*/
export function validateMetaKey(key) {
if (!key) {
return { valid: false, reason: "key is empty" };
}
const slashIndex = key.indexOf("/");
let name;
if (slashIndex !== -1) {
const prefix = key.substring(0, slashIndex);
name = key.substring(slashIndex + 1);
// Validate prefix labels
const labels = prefix.split(".");
for (const label of labels) {
if (!label || !LABEL_PATTERN.test(label)) {
return { valid: false, reason: `invalid prefix label "${label}"` };
}
}
// Check reserved prefixes: second label is "modelcontextprotocol" or "mcp" (per 2025-11-25 spec)
if (labels.length >= 2) {
const secondLabel = labels[1].toLowerCase();
if (secondLabel === "modelcontextprotocol" || secondLabel === "mcp") {
return { valid: true, reserved: true, reason: `prefix "${prefix}/" is reserved for MCP protocol use` };
}
}
}
else {
name = key;
}
// Validate name (empty name is valid per spec when prefix is present)
if (name && !NAME_PATTERN.test(name)) {
return {
valid: false,
reason: `must start/end with alphanumeric, contain only alphanumerics/hyphens/underscores/dots`,
};
}
return { valid: true };
}
/**
* Compute the qualified name for a skill by combining prefix and base name.

@@ -162,3 +108,2 @@ * If prefix is empty, returns the base name unchanged.

const userInvocable = metadata["user-invocable"];
const rawMetadata = metadata["metadata"];
if (typeof name !== "string" || !name.trim()) {

@@ -172,30 +117,2 @@ console.error(`Skill at ${skillDir}: missing or invalid 'name' field`);

}
// Validate metadata: must be a plain object if provided (Agent Skills spec: string keys to string values)
// Keys are validated against MCP _meta naming rules; invalid/reserved keys are warned and skipped.
let skillMetadata;
if (rawMetadata !== undefined && rawMetadata !== null) {
if (typeof rawMetadata === "object" && !Array.isArray(rawMetadata)) {
const validEntries = [];
for (const [k, v] of Object.entries(rawMetadata)) {
const keyStr = String(k);
const validation = validateMetaKey(keyStr);
if (!validation.valid) {
console.error(`Skill at ${skillDir}: skipping metadata key "${keyStr}": ${validation.reason}`);
continue;
}
if (validation.reserved) {
console.error(`Warning: Skill at ${skillDir}: skipping metadata key "${keyStr}": ${validation.reason}`);
continue;
}
// Coerce values to strings per Agent Skills spec
validEntries.push([keyStr, String(v)]);
}
if (validEntries.length > 0) {
skillMetadata = Object.fromEntries(validEntries);
}
}
else {
console.error(`Skill at ${skillDir}: 'metadata' must be a YAML mapping (key-value pairs), got ${Array.isArray(rawMetadata) ? "array" : typeof rawMetadata}`);
}
}
const effectiveAssistant = disableModelInvocation !== true;

@@ -213,3 +130,2 @@ const effectiveUser = userInvocable !== false;

userInvocable: userInvocable !== false, // Default to true
metadata: skillMetadata,
// Initialize effective values from frontmatter (overrides applied later)

@@ -329,3 +245,3 @@ effectiveAssistantInvocable: effectiveAssistant,

/**
* Compute MCP resource annotations for a skill.
* Compute MCP resource annotations and file size for a skill.
* Derives audience from effective invocation flags and sets default priority.

@@ -335,3 +251,3 @@ *

* @param priority - Priority hint (0.0 = least important, 1.0 = most important, default 0.5)
* @returns Annotations object for use on MCP resources
* @returns Object with annotations and optional size (in bytes) for use on MCP resources
*/

@@ -350,10 +266,12 @@ export function getResourceAnnotations(skill, priority = 0.5) {

const annotations = { audience, priority: clampedPriority };
let size;
try {
const stat = fs.statSync(skill.path);
annotations.lastModified = stat.mtime.toISOString();
size = stat.size;
}
catch {
// Skip lastModified if file cannot be stat'd
// Skip lastModified/size if file cannot be stat'd
}
return annotations;
return { annotations, size };
}

@@ -360,0 +278,0 @@ export const SKILL_COUNT_WARNING_THRESHOLD = 50;

@@ -76,2 +76,3 @@ /**

for (const [name, skill] of skillState.skillMap) {
const { annotations, size } = getResourceAnnotations(skill, 0.3);
const resource = {

@@ -82,6 +83,6 @@ uri: `skill://${encodeURIComponent(name)}/`,

description: `All files in ${name} skill directory`,
annotations: getResourceAnnotations(skill),
annotations,
};
if (skill.metadata) {
resource._meta = skill.metadata;
if (size !== undefined) {
resource.size = size;
}

@@ -161,2 +162,3 @@ resources.push(resource);

for (const [name, skill] of skillState.skillMap) {
const { annotations, size } = getResourceAnnotations(skill, 0.8);
const resource = {

@@ -167,6 +169,6 @@ uri: `skill://${encodeURIComponent(name)}`,

description: skill.description,
annotations: getResourceAnnotations(skill),
annotations,
};
if (skill.metadata) {
resource._meta = skill.metadata;
if (size !== undefined) {
resource.size = size;
}

@@ -173,0 +175,0 @@ resources.push(resource);

@@ -73,3 +73,3 @@ /**

const content = loadSkillContent(skill.path);
const result = {
return {
content: [

@@ -82,6 +82,2 @@ {

};
if (skill.metadata) {
result._meta = skill.metadata;
}
return result;
}

@@ -320,7 +316,3 @@ catch (error) {

}
const dirResult = { content: contents };
if (skill.metadata) {
dirResult._meta = skill.metadata;
}
return dirResult;
return { content: contents };
}

@@ -356,3 +348,3 @@ // Check file size to prevent memory exhaustion

const content = fs.readFileSync(fullPath, "utf-8");
const fileResult = {
return {
content: [

@@ -365,6 +357,2 @@ {

};
if (skill.metadata) {
fileResult._meta = skill.metadata;
}
return fileResult;
}

@@ -371,0 +359,0 @@ catch (error) {

{
"name": "@skilljack/mcp",
"version": "0.9.0",
"version": "0.10.0",
"description": "MCP server that discovers and serves Agent Skills. I know kung fu.",

@@ -60,3 +60,3 @@ "type": "module",

"@modelcontextprotocol/ext-apps": "^1.0.0",
"@modelcontextprotocol/sdk": "^1.25.3",
"@modelcontextprotocol/sdk": "^1.29.0",
"chokidar": "^5.0.0",

@@ -63,0 +63,0 @@ "simple-git": "^3.27.0",

@@ -369,3 +369,2 @@ ---

| `user-invocable: false` | Yes | No | Background context (model auto-loads when relevant) |
| `metadata: { key: value }` | — | — | Arbitrary key-value pairs passed as `_meta` on MCP primitives |

@@ -396,18 +395,2 @@ ### Example: User-Only Skill

### Skill Metadata
The optional `metadata` frontmatter field allows attaching arbitrary key-value pairs to a skill, following the [Agent Skills spec](https://agentskills.io/specification). Values are coerced to strings. The metadata is translated to `_meta` on MCP resources and tool results.
```yaml
---
name: my-skill
description: A helpful skill
metadata:
author: example-org
version: "1.0"
---
```
When this skill's resources are listed or its content is loaded via the `skill` or `skill-resource` tools, the response includes `_meta: { author: "example-org", version: "1.0" }`.
Note: Resources (`skill://` URIs) always include all skills regardless of visibility settings, allowing explicit access when needed.

@@ -414,0 +397,0 @@

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display