@baitong-dev/skills-mcp 0.0.1 → 0.0.3

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.
@@ -0,0 +1 @@
1
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,172 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
7
+ const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
8
+ const fs_1 = __importDefault(require("fs"));
9
+ const path_1 = __importDefault(require("path"));
10
+ const zod_1 = __importDefault(require("zod"));
11
+ const mcp_helpers_1 = require("@baitong-dev/mcp-helpers");
12
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
13
+ const yamlFront = require('yaml-front-matter');
14
+ const MCP_NAME = 'Skills MCP';
15
+ const MCP_VERSION = '0.0.3';
16
+ function getSkillsRootDir() {
17
+ const dir = path_1.default.join(mcp_helpers_1.MCP_HOME_DIR, 'skills');
18
+ if (!fs_1.default.existsSync(dir)) {
19
+ fs_1.default.mkdirSync(dir, {
20
+ recursive: true
21
+ });
22
+ }
23
+ return dir;
24
+ }
25
+ function loadSkillsMetadata() {
26
+ const skillsRootDirs = fs_1.default.readdirSync(getSkillsRootDir());
27
+ let skillConfigs;
28
+ try {
29
+ skillConfigs =
30
+ JSON.parse(fs_1.default.readFileSync(path_1.default.join(mcp_helpers_1.MCP_HOME_DIR, 'config', 'skills_config.json'), 'utf-8'))
31
+ .skills || [];
32
+ }
33
+ catch (_) {
34
+ skillConfigs = [];
35
+ }
36
+ return skillsRootDirs
37
+ .map(skillDir => {
38
+ try {
39
+ const { name, description } = readSkillJson(getSkillFilePath(skillDir));
40
+ const isActive = skillConfigs.find(skill => skill.name === name)?.isActive ?? true;
41
+ return {
42
+ name,
43
+ description,
44
+ cwd: getSkillCwd(skillDir),
45
+ isActive
46
+ };
47
+ }
48
+ catch (_) {
49
+ return null;
50
+ }
51
+ })
52
+ .filter(item => item !== null);
53
+ }
54
+ function getSkillCwd(skillName) {
55
+ return path_1.default.join(getSkillsRootDir(), skillName);
56
+ }
57
+ function getSkillFilePath(skillName) {
58
+ return path_1.default.join(getSkillCwd(skillName), 'SKILL.md');
59
+ }
60
+ function readSkillJson(skillFilePath) {
61
+ const skillMd = fs_1.default.readFileSync(skillFilePath, 'utf-8');
62
+ const { name, description, __content } = yamlFront.loadFront(skillMd);
63
+ return {
64
+ name: name.toString().trim(),
65
+ description: description.toString().trim(),
66
+ __content: __content.trim()
67
+ };
68
+ }
69
+ function registerLoadSkill(server) {
70
+ const metadata = loadSkillsMetadata().filter(skill => skill.isActive);
71
+ const examples = metadata
72
+ .map(skill => `'${skill.name}'`)
73
+ .slice(0, 3)
74
+ .join(', ');
75
+ const hint = examples.length > 0 ? ` (e.g., ${examples}, ...)` : '';
76
+ server.registerTool('load_skill', {
77
+ description: metadata.length === 0
78
+ ? 'Load a specialized skill that provides domain-specific instructions and workflows. No skills are currently available.'
79
+ : `Load a specialized skill that provides domain-specific instructions and workflows.
80
+
81
+ When you recognize that a task matches one of the available skills listed below, use this tool to load the full skill instructions.
82
+
83
+ The skill will inject detailed instructions, workflows, and access to bundled resources (scripts, references, templates) into the conversation context.
84
+
85
+ The following skills provide specialized sets of instructions for particular tasks.
86
+
87
+ Invoke this tool to load a skill when a task matches one of the available skills listed below:
88
+
89
+ Available skills:
90
+
91
+ ${metadata.map(skill => `- ${skill.name}: ${skill.description}`).join('\n')}`,
92
+ inputSchema: zod_1.default.object({
93
+ skillName: zod_1.default.string().describe(`The name of the skill from available skills${hint}`),
94
+ toolInfo: mcp_helpers_1.Tool.Info
95
+ })
96
+ }, async ({ skillName }) => {
97
+ if (metadata.length === 0) {
98
+ return {
99
+ isError: true,
100
+ content: [
101
+ {
102
+ type: 'text',
103
+ text: 'No skills are currently available.'
104
+ }
105
+ ]
106
+ };
107
+ }
108
+ if (!metadata.some(skill => skill.name === skillName)) {
109
+ return {
110
+ isError: true,
111
+ content: [
112
+ {
113
+ type: 'text',
114
+ text: `Skill "${skillName}" not found.`
115
+ }
116
+ ]
117
+ };
118
+ }
119
+ // 返回skill元素据,只包含name和description
120
+ const skillCwd = getSkillCwd(skillName);
121
+ const skillFilePath = getSkillFilePath(skillName);
122
+ const json = readSkillJson(skillFilePath);
123
+ return {
124
+ content: [
125
+ {
126
+ type: 'text',
127
+ text: `Base directory for this skill: ${skillCwd}
128
+
129
+ ${json.__content}`
130
+ }
131
+ ]
132
+ };
133
+ });
134
+ }
135
+ function registerRefreshSkills(server) {
136
+ server.registerTool('reload_skills', {
137
+ description: `Reload skills metadata. Especially after creating a new skill.`, // 尤其在创建技能后要调用下重新加载技能
138
+ inputSchema: zod_1.default.object({
139
+ toolInfo: mcp_helpers_1.Tool.Info
140
+ })
141
+ }, async () => {
142
+ return {
143
+ content: [
144
+ {
145
+ type: 'text',
146
+ text: `Skills metadata reloaded.`
147
+ }
148
+ ]
149
+ };
150
+ });
151
+ }
152
+ // 创建MCP服务器实例
153
+ const server = new mcp_js_1.McpServer({
154
+ name: MCP_NAME,
155
+ version: MCP_VERSION
156
+ }, {
157
+ capabilities: {
158
+ logging: {}
159
+ }
160
+ });
161
+ // 注册所有工具
162
+ registerLoadSkill(server);
163
+ registerRefreshSkills(server);
164
+ async function main() {
165
+ const transport = new stdio_js_1.StdioServerTransport();
166
+ await server.connect(transport);
167
+ console.error(`${MCP_NAME} Server v${MCP_VERSION} running`);
168
+ }
169
+ main().catch(error => {
170
+ console.error('Fatal error in main():', error);
171
+ process.exit(1);
172
+ });
package/package.json CHANGED
@@ -1,15 +1,14 @@
1
1
  {
2
2
  "name": "@baitong-dev/skills-mcp",
3
- "version": "0.0.1",
4
- "main": "SkillsMcpServer.js",
3
+ "version": "0.0.3",
4
+ "main": "./dist/index.js",
5
5
  "bin": {
6
- "@baitong-dev/skills-mcp": "./SkillsMcpServer.js"
6
+ "@baitong-dev/skills-mcp": "./dist/index.js"
7
7
  },
8
8
  "files": [
9
- "SkillsMcpServer.js",
9
+ "dist",
10
10
  "README.md"
11
11
  ],
12
- "scripts": {},
13
12
  "keywords": [
14
13
  "mcp",
15
14
  "skills"
@@ -19,10 +18,17 @@
19
18
  "@modelcontextprotocol/sdk": "^1.25.1",
20
19
  "commander": "^14.0.3",
21
20
  "yaml-front-matter": "^4.1.1",
22
- "zod": "^4.3.4"
21
+ "zod": "^4.3.4",
22
+ "@baitong-dev/mcp-helpers": "0.0.5"
23
+ },
24
+ "devDependencies": {
25
+ "typescript": "^5.9.2"
23
26
  },
24
27
  "publishConfig": {
25
28
  "access": "public",
26
29
  "registry": "https://registry.npmjs.org"
30
+ },
31
+ "scripts": {
32
+ "tsc": "tsc ./src/index.ts --declaration --module commonjs --target es2021 --esModuleInterop --outDir ./dist"
27
33
  }
28
- }
34
+ }
@@ -1,201 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
7
- const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
8
- const streamableHttp_js_1 = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
9
- const fs_1 = __importDefault(require("fs"));
10
- const http_1 = require("http");
11
- const path_1 = __importDefault(require("path"));
12
- const zod_1 = __importDefault(require("zod"));
13
- const commander_1 = require("commander");
14
- // eslint-disable-next-line @typescript-eslint/no-require-imports
15
- const yamlFront = require('yaml-front-matter');
16
- // 命令行参数处理
17
- const program = new commander_1.Command()
18
- .option('--transport <stdio|http|sse>', 'transport type', 'stdio')
19
- .option('--port <number>', 'port for HTTP/SSE transport', '3000')
20
- .parse(process.argv);
21
- const cliOptions = program.opts();
22
- const allowedTransports = ['stdio', 'http'];
23
- if (!allowedTransports.includes(cliOptions.transport)) {
24
- console.error(`Invalid --transport value: '${cliOptions.transport}'. Must be one of: stdio, http, sse.`);
25
- process.exit(1);
26
- }
27
- const MCP_NAME = 'Skills MCP';
28
- const MCP_VERSION = '0.0.1';
29
- const PORT = parseInt(cliOptions.port, 10);
30
- const CONFIG_DIR = process.env.MCP_HOME_DIR;
31
- const TRANSPORT_TYPE = (cliOptions.transport || 'stdio');
32
- const logInfo = (...args) => {
33
- if (TRANSPORT_TYPE === 'stdio') {
34
- console.error(...args);
35
- }
36
- else {
37
- console.log(...args);
38
- }
39
- };
40
- function getSkillsRootDir() {
41
- const dir = path_1.default.join(CONFIG_DIR, 'skills');
42
- if (!fs_1.default.existsSync(dir)) {
43
- fs_1.default.mkdirSync(dir, {
44
- recursive: true
45
- });
46
- }
47
- return dir;
48
- }
49
- function loadSkillsMetadatas() {
50
- const skillsRootDirs = fs_1.default.readdirSync(getSkillsRootDir());
51
- return skillsRootDirs
52
- .map(skillName => {
53
- try {
54
- const { name, description } = readSkillJson(getSkillFilePath(skillName));
55
- return {
56
- name: name.trim(),
57
- description: description.trim()
58
- };
59
- }
60
- catch (_) {
61
- return null;
62
- }
63
- })
64
- .filter(item => item !== null);
65
- }
66
- function getSkillCwd(skillName) {
67
- return path_1.default.join(getSkillsRootDir(), skillName);
68
- }
69
- function getSkillFilePath(skillName) {
70
- return path_1.default.join(getSkillCwd(skillName), 'SKILL.md');
71
- }
72
- function readSkillJson(skillFilePath) {
73
- const skillMd = fs_1.default.readFileSync(skillFilePath, 'utf-8');
74
- return yamlFront.loadFront(skillMd);
75
- }
76
- function registerLoadSkill(server) {
77
- server.registerTool('load_skill', {
78
- description: `
79
- Available skills:
80
- ${loadSkillsMetadatas()
81
- .map(skill => `- ${skill.name}: ${skill.description}`)
82
- .join('\n')}
83
-
84
- Returns the skill's prompt and context.`,
85
- inputSchema: zod_1.default.object({
86
- skillName: zod_1.default.string().describe('Name of skill to load')
87
- })
88
- }, async ({ skillName }) => {
89
- // 返回skill元素据,只包含name和description
90
- const skillCwd = getSkillCwd(skillName);
91
- const skillFilePath = getSkillFilePath(skillName);
92
- const json = readSkillJson(skillFilePath);
93
- return {
94
- content: [
95
- {
96
- type: 'text',
97
- text: `Base directory for this skill: ${skillCwd}
98
-
99
- ${json.__content.trim()}`
100
- }
101
- ]
102
- };
103
- });
104
- }
105
- // 创建MCP服务器实例
106
- function createServerInstance() {
107
- const server = new mcp_js_1.McpServer({
108
- name: MCP_NAME,
109
- version: MCP_VERSION
110
- }, {
111
- capabilities: {
112
- logging: {}
113
- }
114
- });
115
- // 注册所有工具
116
- registerLoadSkill(server);
117
- return server;
118
- }
119
- async function main() {
120
- if (TRANSPORT_TYPE === 'http') {
121
- const httpServer = (0, http_1.createServer)(async (req, res) => {
122
- const url = new URL(req.url || '', `http://${req.headers.host}`).pathname;
123
- // 设置 CORS 头
124
- res.setHeader('Access-Control-Allow-Origin', '*');
125
- res.setHeader('Access-Control-Allow-Methods', 'GET,POST,OPTIONS,DELETE');
126
- res.setHeader('Access-Control-Allow-Headers', 'Content-Type, MCP-Session-Id, mcp-session-id');
127
- // 处理预检请求
128
- if (req.method === 'OPTIONS') {
129
- res.writeHead(200);
130
- res.end();
131
- return;
132
- }
133
- try {
134
- // 为每个请求创建新的服务器实例
135
- const requestServer = createServerInstance();
136
- if (url === '/mcp') {
137
- const transport = new streamableHttp_js_1.StreamableHTTPServerTransport({
138
- sessionIdGenerator: undefined
139
- });
140
- await requestServer.connect(transport);
141
- await transport.handleRequest(req, res);
142
- }
143
- else if (url === '/health') {
144
- res.writeHead(200, { 'Content-Type': 'application/json' });
145
- res.end(JSON.stringify({ status: 'ok', transport: TRANSPORT_TYPE }));
146
- }
147
- else if (url === '/info') {
148
- res.writeHead(200, { 'Content-Type': 'application/json' });
149
- res.end(JSON.stringify({
150
- name: `${MCP_NAME} Server`,
151
- version: MCP_VERSION,
152
- transport: TRANSPORT_TYPE,
153
- endpoints: {
154
- mcp: '/mcp',
155
- health: '/health',
156
- info: '/info'
157
- }
158
- }));
159
- }
160
- else {
161
- res.writeHead(404);
162
- res.end('Not found');
163
- }
164
- }
165
- catch (error) {
166
- console.error('Error handling request: ', error);
167
- if (!res.headersSent) {
168
- res.writeHead(500);
169
- res.end('Internal Server Error');
170
- }
171
- }
172
- });
173
- httpServer.listen(PORT, () => {
174
- logInfo(`${MCP_NAME} ${TRANSPORT_TYPE.toUpperCase()} server started.`);
175
- logInfo(`MCP endpoint: http://localhost:${PORT}/mcp`);
176
- logInfo(`Health: http://localhost:${PORT}/health`);
177
- logInfo(`Info: http://localhost:${PORT}/info`);
178
- });
179
- }
180
- else {
181
- // stdio 模式
182
- const server = createServerInstance();
183
- const transport = new stdio_js_1.StdioServerTransport();
184
- await server.connect(transport);
185
- logInfo(`${MCP_NAME} ${TRANSPORT_TYPE.toUpperCase()} server started.`);
186
- }
187
- }
188
- // 优雅关闭
189
- process.on('SIGINT', async () => {
190
- logInfo(`\nShutting down ${MCP_NAME} server...`);
191
- process.exit(0);
192
- });
193
- process.on('SIGTERM', async () => {
194
- logInfo(`\nReceived termination signal, shutting down ${MCP_NAME} server...`);
195
- process.exit(0);
196
- });
197
- // 启动服务器
198
- main().catch(error => {
199
- logInfo('Starting server failed:', error);
200
- process.exit(1);
201
- });