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.4.0
to
0.5.0
+5
-12
dist/index.js

@@ -21,3 +21,2 @@ #!/usr/bin/env node

import { registerSkillResources } from "./skill-resources.js";
import { registerSkillPrompts, refreshPrompts } from "./skill-prompts.js";
import { createSubscriptionManager, registerSubscriptionHandlers, refreshSubscriptions, } from "./subscriptions.js";

@@ -119,6 +118,5 @@ /**

* @param skillTool - The registered skill tool to update
* @param promptRegistry - For refreshing skill prompts
* @param subscriptionManager - For refreshing resource subscriptions
*/
function refreshSkills(skillsDirs, server, skillTool, promptRegistry, subscriptionManager) {
function refreshSkills(skillsDirs, server, skillTool, subscriptionManager) {
console.error("Refreshing skills...");

@@ -135,4 +133,2 @@ // Re-discover all skills

});
// Refresh prompts to match new skill state
refreshPrompts(server, skillState, promptRegistry);
// Refresh resource subscriptions to match new skill state

@@ -158,6 +154,5 @@ refreshSubscriptions(subscriptionManager, skillState, (uri) => {

* @param skillTool - The registered skill tool to update
* @param promptRegistry - For refreshing skill prompts
* @param subscriptionManager - For refreshing subscriptions
*/
function watchSkillDirectories(skillsDirs, server, skillTool, promptRegistry, subscriptionManager) {
function watchSkillDirectories(skillsDirs, server, skillTool, subscriptionManager) {
let refreshTimeout = null;

@@ -170,3 +165,3 @@ const debouncedRefresh = () => {

refreshTimeout = null;
refreshSkills(skillsDirs, server, skillTool, promptRegistry, subscriptionManager);
refreshSkills(skillsDirs, server, skillTool, subscriptionManager);
}, SKILL_REFRESH_DEBOUNCE_MS);

@@ -264,13 +259,11 @@ };

resources: { subscribe: true, listChanged: true },
prompts: { listChanged: true },
},
});
// Register tools, resources, and prompts
// Register tools and resources
const skillTool = registerSkillTool(server, skillState);
registerSkillResources(server, skillState);
const promptRegistry = registerSkillPrompts(server, skillState);
// Register subscription handlers for resource file watching
registerSubscriptionHandlers(server, skillState, subscriptionManager);
// Set up file watchers for skill directory changes
watchSkillDirectories(skillsDirs, server, skillTool, promptRegistry, subscriptionManager);
watchSkillDirectories(skillsDirs, server, skillTool, subscriptionManager);
// Connect via stdio transport

@@ -277,0 +270,0 @@ const transport = new StdioServerTransport();

@@ -11,8 +11,5 @@ /**

* URI Scheme:
* skill://{skillName} -> SKILL.md content (template)
* skill://{skillName}/ -> Collection: all files in skill directory
*
* Note: Individual file URIs (skill://{skillName}/{path}) are not listed
* as resources to reduce noise. Use the skill-resource tool to fetch
* specific files on demand.
* skill://{skillName} -> SKILL.md content (template)
* skill://{skillName}/ -> Collection: all files in skill directory
* skill://{skillName}/{path} -> File within skill directory (template)
*/

@@ -19,0 +16,0 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";

@@ -11,8 +11,5 @@ /**

* URI Scheme:
* skill://{skillName} -> SKILL.md content (template)
* skill://{skillName}/ -> Collection: all files in skill directory
*
* Note: Individual file URIs (skill://{skillName}/{path}) are not listed
* as resources to reduce noise. Use the skill-resource tool to fetch
* specific files on demand.
* skill://{skillName} -> SKILL.md content (template)
* skill://{skillName}/ -> Collection: all files in skill directory
* skill://{skillName}/{path} -> File within skill directory (template)
*/

@@ -57,7 +54,6 @@ import * as fs from "node:fs";

registerSkillTemplate(server, skillState);
// Register collection resource for skill directories
// Register collection resource for skill directories (must be before file template)
registerSkillDirectoryCollection(server, skillState);
// Note: Individual file resources (skill://{name}/{path}) are intentionally
// not registered to reduce noise. Use the skill-resource tool to fetch
// specific files on demand.
// Register resource template for skill files
registerSkillFileTemplate(server, skillState);
}

@@ -205,1 +201,89 @@ /**

}
/**
* Register the resource template for accessing files within skills.
*
* URI Pattern: skill://{skillName}/{filePath}
*/
function registerSkillFileTemplate(server, skillState) {
server.registerResource("Skill File", new ResourceTemplate("skill://{skillName}/{+filePath}", {
list: async () => {
// Return all listable skill files (dynamic based on current skillMap)
const resources = [];
for (const [name, skill] of skillState.skillMap) {
const skillDir = path.dirname(skill.path);
const files = listSkillFiles(skillDir);
for (const file of files) {
const uri = `skill://${encodeURIComponent(name)}/${file}`;
resources.push({
uri,
name: `${name}/${file}`,
mimeType: getMimeType(file),
});
}
}
return { resources };
},
complete: {
skillName: (value) => {
const names = Array.from(skillState.skillMap.keys());
return names.filter((name) => name.toLowerCase().startsWith(value.toLowerCase()));
},
},
}), {
mimeType: "text/plain",
description: "Files within a skill directory (scripts, snippets, assets, etc.)",
}, async (resourceUri, variables) => {
// Extract skill name and file path from URI
const uriStr = resourceUri.toString();
const match = uriStr.match(/^skill:\/\/([^/]+)\/(.+)$/);
if (!match) {
throw new Error(`Invalid skill file URI: ${uriStr}`);
}
const skillName = decodeURIComponent(match[1]);
const filePath = match[2];
const skill = skillState.skillMap.get(skillName);
if (!skill) {
const available = Array.from(skillState.skillMap.keys()).join(", ");
throw new Error(`Skill "${skillName}" not found. Available: ${available || "none"}`);
}
const skillDir = path.dirname(skill.path);
const fullPath = path.resolve(skillDir, filePath);
// Security: Validate path is within skill directory
if (!isPathWithinBase(fullPath, skillDir)) {
throw new Error(`Path "${filePath}" is outside the skill directory`);
}
// Check file exists
if (!fs.existsSync(fullPath)) {
const files = listSkillFiles(skillDir).slice(0, 10);
throw new Error(`File "${filePath}" not found in skill "${skillName}". ` +
`Available: ${files.join(", ")}${files.length >= 10 ? "..." : ""}`);
}
const stat = fs.statSync(fullPath);
// Reject symlinks
if (stat.isSymbolicLink()) {
throw new Error(`Cannot read symlink "${filePath}"`);
}
// Reject directories
if (stat.isDirectory()) {
const files = listSkillFiles(skillDir, filePath);
throw new Error(`"${filePath}" is a directory. Files within: ${files.join(", ")}`);
}
// Check file size
if (stat.size > MAX_FILE_SIZE) {
const sizeMB = (stat.size / 1024 / 1024).toFixed(2);
throw new Error(`File too large (${sizeMB}MB). Maximum: 10MB`);
}
// Read and return content
const content = fs.readFileSync(fullPath, "utf-8");
const mimeType = getMimeType(fullPath);
return {
contents: [
{
uri: uriStr,
mimeType,
text: content,
},
],
};
});
}

@@ -36,3 +36,7 @@ /**

const usage = "Load a skill's full instructions. Returns the complete SKILL.md content " +
"with step-by-step guidance, examples, and file references to follow.\n\n";
"with step-by-step guidance, examples, and file references to follow.\n\n" +
"IMPORTANT: When a skill is relevant to the user's task, you must invoke this tool " +
"IMMEDIATELY as your first action. NEVER just announce or mention a skill without " +
"actually calling this tool. This is a BLOCKING REQUIREMENT: invoke this tool BEFORE " +
"generating any other response about the task.\n\n";
const skills = Array.from(skillState.skillMap.values());

@@ -39,0 +43,0 @@ return usage + generateInstructions(skills);

@@ -12,3 +12,3 @@ /**

* - skill://{name}/ → Watch entire skill directory (directory collection)
* - skill://{name}/{path} → Watch specific file (subscribable but not listed as resource)
* - skill://{name}/{path} → Watch specific file
*/

@@ -15,0 +15,0 @@ import { FSWatcher } from "chokidar";

@@ -12,3 +12,3 @@ /**

* - skill://{name}/ → Watch entire skill directory (directory collection)
* - skill://{name}/{path} → Watch specific file (subscribable but not listed as resource)
* - skill://{name}/{path} → Watch specific file
*/

@@ -15,0 +15,0 @@ import chokidar from "chokidar";

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

@@ -41,3 +41,11 @@ "type": "module",

"dev": "tsx watch src/index.ts",
"inspector": "npx @modelcontextprotocol/inspector@latest node dist/index.js"
"inspector": "npx @modelcontextprotocol/inspector@latest node dist/index.js",
"eval": "tsx evals/eval.ts",
"eval:greeting": "tsx evals/eval.ts --task=greeting",
"eval:code-style": "tsx evals/eval.ts --task=code-style",
"eval:template": "tsx evals/eval.ts --task=template-generator",
"eval:xlsx-openpyxl": "tsx evals/eval.ts --task=xlsx-openpyxl",
"eval:xlsx-formulas": "tsx evals/eval.ts --task=xlsx-formulas",
"eval:xlsx-financial": "tsx evals/eval.ts --task=xlsx-financial",
"eval:xlsx-verify": "tsx evals/eval.ts --task=xlsx-verify"
},

@@ -51,2 +59,3 @@ "dependencies": {

"devDependencies": {
"@anthropic-ai/claude-agent-sdk": "^0.1.42",
"@types/node": "^22.10.0",

@@ -53,0 +62,0 @@ "tsx": "^4.19.2",