ring-skills-mcp 1.0.4 → 1.0.6
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/dist/index.js +74 -56
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -8,7 +8,6 @@ import path from "path";
|
|
|
8
8
|
const execAsync = promisify(exec);
|
|
9
9
|
// Configuration
|
|
10
10
|
const DEFAULT_API_HOST = process.env.SKILLS_API_HOST || "";
|
|
11
|
-
const DEFAULT_SKILLS_REPO = process.env.SKILLS_REPO || "anthropics/skills";
|
|
12
11
|
const DEFAULT_AUTH_TOKEN = process.env.SKILLS_AUTH_TOKEN || "";
|
|
13
12
|
// Create MCP server
|
|
14
13
|
const server = new McpServer({
|
|
@@ -52,30 +51,85 @@ async function fetchSkills(host, search = "", pageSize = 5000, token = "") {
|
|
|
52
51
|
throw new Error("Failed to fetch Skills list: Unknown error");
|
|
53
52
|
}
|
|
54
53
|
}
|
|
54
|
+
function parseGitUrl(gitUrl) {
|
|
55
|
+
// GitHub URL pattern: https://github.com/{owner}/{repo}/tree/{branch}/{path}
|
|
56
|
+
const githubMatch = gitUrl.match(/https?:\/\/github\.com\/([^\/]+)\/([^\/]+)\/tree\/([^\/]+)\/(.+)/);
|
|
57
|
+
if (githubMatch) {
|
|
58
|
+
const skillPath = githubMatch[4];
|
|
59
|
+
const skillName = skillPath.split("/").pop() || "";
|
|
60
|
+
return {
|
|
61
|
+
type: "github",
|
|
62
|
+
host: "github.com",
|
|
63
|
+
owner: githubMatch[1],
|
|
64
|
+
repo: githubMatch[2],
|
|
65
|
+
branch: githubMatch[3],
|
|
66
|
+
skillPath,
|
|
67
|
+
skillName: skillName.toLowerCase(),
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
// GitLab URL pattern: https://{host}/{owner}/{repo}/-/tree/{branch}/{path}
|
|
71
|
+
const gitlabMatch = gitUrl.match(/https?:\/\/([^\/]+)\/([^\/]+)\/([^\/]+)\/-\/tree\/([^\/]+)\/(.+)/);
|
|
72
|
+
if (gitlabMatch) {
|
|
73
|
+
const skillPath = gitlabMatch[5];
|
|
74
|
+
const skillName = skillPath.split("/").pop() || "";
|
|
75
|
+
return {
|
|
76
|
+
type: "gitlab",
|
|
77
|
+
host: gitlabMatch[1],
|
|
78
|
+
owner: gitlabMatch[2],
|
|
79
|
+
repo: gitlabMatch[3],
|
|
80
|
+
branch: gitlabMatch[4],
|
|
81
|
+
skillPath,
|
|
82
|
+
skillName: skillName.toLowerCase(),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
// Fallback: try to extract skill name from URL
|
|
86
|
+
const skillMatch = gitUrl.match(/\/skills\/([^\/\s]+)/);
|
|
87
|
+
if (skillMatch) {
|
|
88
|
+
return {
|
|
89
|
+
type: "unknown",
|
|
90
|
+
host: "",
|
|
91
|
+
owner: "",
|
|
92
|
+
repo: "",
|
|
93
|
+
branch: "main",
|
|
94
|
+
skillPath: "",
|
|
95
|
+
skillName: skillMatch[1].toLowerCase(),
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
55
100
|
/**
|
|
56
|
-
*
|
|
57
|
-
* Example: "https://github.com/anthropics/skills/tree/main/skills/webapp-testing" -> "webapp-testing"
|
|
101
|
+
* Build degit source path from git URL info
|
|
58
102
|
*/
|
|
59
|
-
function
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
103
|
+
function buildDegitSource(urlInfo) {
|
|
104
|
+
if (urlInfo.type === "github") {
|
|
105
|
+
// GitHub: owner/repo/path#branch
|
|
106
|
+
return `${urlInfo.owner}/${urlInfo.repo}/${urlInfo.skillPath}#${urlInfo.branch}`;
|
|
107
|
+
}
|
|
108
|
+
else if (urlInfo.type === "gitlab") {
|
|
109
|
+
// GitLab (self-hosted): host:owner/repo/path#branch
|
|
110
|
+
return `${urlInfo.host}:${urlInfo.owner}/${urlInfo.repo}/${urlInfo.skillPath}#${urlInfo.branch}`;
|
|
111
|
+
}
|
|
112
|
+
return "";
|
|
63
113
|
}
|
|
64
114
|
/**
|
|
65
|
-
* Install Skill to specified project
|
|
115
|
+
* Install Skill to specified project using gitUrl
|
|
66
116
|
*/
|
|
67
|
-
async function
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
const
|
|
117
|
+
async function installSkillFromGitUrl(gitUrl, projectPath, targetDir = ".claude/skills") {
|
|
118
|
+
const urlInfo = parseGitUrl(gitUrl);
|
|
119
|
+
if (!urlInfo) {
|
|
120
|
+
throw new Error(`Could not parse git URL: ${gitUrl}`);
|
|
121
|
+
}
|
|
122
|
+
const skillName = urlInfo.skillName;
|
|
123
|
+
const destPath = path.join(projectPath, targetDir, skillName);
|
|
124
|
+
const sourcePath = buildDegitSource(urlInfo);
|
|
125
|
+
if (!sourcePath) {
|
|
126
|
+
throw new Error(`Could not build degit source from URL: ${gitUrl}`);
|
|
127
|
+
}
|
|
74
128
|
const command = `npx degit ${sourcePath} ${destPath}`;
|
|
75
129
|
try {
|
|
76
130
|
const { stdout, stderr } = await execAsync(command);
|
|
77
131
|
const output = stdout || stderr || "Installation completed";
|
|
78
|
-
return `✅ Skill "${
|
|
132
|
+
return `✅ Skill "${skillName}" has been successfully installed to ${destPath}\n\nSource: ${sourcePath}\n${output}`;
|
|
79
133
|
}
|
|
80
134
|
catch (error) {
|
|
81
135
|
if (error instanceof Error) {
|
|
@@ -154,9 +208,8 @@ server.tool("list_skills", "Fetch company Skills list. You can search for specif
|
|
|
154
208
|
}
|
|
155
209
|
});
|
|
156
210
|
// Register tool: Install Skill
|
|
157
|
-
server.tool("install_skill", "Install skill to local project by
|
|
158
|
-
|
|
159
|
-
gitUrl: z.string().optional().describe("Git URL of the skill (e.g., 'https://github.com/anthropics/skills/tree/main/skills/webapp-testing'). The skill name will be extracted from this URL. Either skillName or gitUrl must be provided."),
|
|
211
|
+
server.tool("install_skill", "Install skill to local project by gitUrl. Uses npx degit to download skill template from remote repository. Project path is required, which can be the project path of the currently opened file in IDE.", {
|
|
212
|
+
gitUrl: z.string().describe("Git URL of the skill (e.g., 'https://github.com/anthropics/skills/tree/main/skills/webapp-testing'). The skill name will be extracted from this URL."),
|
|
160
213
|
projectPath: z
|
|
161
214
|
.string()
|
|
162
215
|
.optional()
|
|
@@ -165,11 +218,7 @@ server.tool("install_skill", "Install skill to local project by skill name or gi
|
|
|
165
218
|
.string()
|
|
166
219
|
.optional()
|
|
167
220
|
.describe("Installation target directory, default: .claude/skills"),
|
|
168
|
-
|
|
169
|
-
.string()
|
|
170
|
-
.optional()
|
|
171
|
-
.describe(`Skills repository path, default: ${DEFAULT_SKILLS_REPO}`),
|
|
172
|
-
}, async ({ skillName, gitUrl, projectPath, targetDir, repo }) => {
|
|
221
|
+
}, async ({ gitUrl, projectPath, targetDir }) => {
|
|
173
222
|
// Check if project path is provided
|
|
174
223
|
if (!projectPath) {
|
|
175
224
|
return {
|
|
@@ -182,39 +231,8 @@ server.tool("install_skill", "Install skill to local project by skill name or gi
|
|
|
182
231
|
isError: true,
|
|
183
232
|
};
|
|
184
233
|
}
|
|
185
|
-
// Determine the actual skill name to use
|
|
186
|
-
let actualSkillName = null;
|
|
187
|
-
// Priority: gitUrl > skillName
|
|
188
|
-
if (gitUrl) {
|
|
189
|
-
actualSkillName = extractSkillNameFromGitUrl(gitUrl);
|
|
190
|
-
if (!actualSkillName) {
|
|
191
|
-
return {
|
|
192
|
-
content: [
|
|
193
|
-
{
|
|
194
|
-
type: "text",
|
|
195
|
-
text: `❌ Error: Could not extract skill name from gitUrl: ${gitUrl}\n\nExpected format: https://github.com/org/repo/tree/main/skills/{skill-name}`,
|
|
196
|
-
},
|
|
197
|
-
],
|
|
198
|
-
isError: true,
|
|
199
|
-
};
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
else if (skillName) {
|
|
203
|
-
actualSkillName = skillName;
|
|
204
|
-
}
|
|
205
|
-
if (!actualSkillName) {
|
|
206
|
-
return {
|
|
207
|
-
content: [
|
|
208
|
-
{
|
|
209
|
-
type: "text",
|
|
210
|
-
text: "❌ Error: Either skillName or gitUrl must be provided.",
|
|
211
|
-
},
|
|
212
|
-
],
|
|
213
|
-
isError: true,
|
|
214
|
-
};
|
|
215
|
-
}
|
|
216
234
|
try {
|
|
217
|
-
const result = await
|
|
235
|
+
const result = await installSkillFromGitUrl(gitUrl, projectPath, targetDir || ".claude/skills");
|
|
218
236
|
return {
|
|
219
237
|
content: [
|
|
220
238
|
{
|