@snippkit/mcp-server 0.0.0-stage → 1.0.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 SnippKit
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,103 @@
1
- # Temporary Holding Version
1
+ # @snippkit/mcp-server
2
+
3
+ Official **Model Context Protocol (MCP)** server for **SnippKit**.
4
+
5
+ Connect your AI coding agents (**Google Antigravity**, **Cursor**, **Claude Desktop**, **Windsurf**, **Claude Code**) directly to your SnippKit library. Empower your agents with procedural **AI Skills**, verified **Code Snippets**, and executable **Shell Commands**.
6
+
7
+ ---
8
+
9
+ ## ⚡ Quick Start
10
+
11
+ ### 1. Cursor Setup
12
+ Add the following to your Cursor MCP settings (`.cursor/mcp.json` or Settings > Features > MCP):
13
+
14
+ ```json
15
+ {
16
+ "mcpServers": {
17
+ "snippkit": {
18
+ "command": "npx",
19
+ "args": ["-y", "@snippkit/mcp-server"],
20
+ "env": {
21
+ "SNIPPKIT_API_KEY": "sk_live_YOUR_API_KEY"
22
+ }
23
+ }
24
+ }
25
+ }
26
+ ```
27
+
28
+ ### 2. Claude Desktop Setup
29
+ Add to `claude_desktop_config.json`:
30
+
31
+ ```json
32
+ {
33
+ "mcpServers": {
34
+ "snippkit": {
35
+ "command": "npx",
36
+ "args": ["-y", "@snippkit/mcp-server"],
37
+ "env": {
38
+ "SNIPPKIT_API_KEY": "sk_live_YOUR_API_KEY"
39
+ }
40
+ }
41
+ }
42
+ }
43
+ ```
44
+
45
+ ### 3. Antigravity & Windsurf Setup
46
+ In your MCP configuration file:
47
+
48
+ ```json
49
+ {
50
+ "mcpServers": {
51
+ "snippkit": {
52
+ "command": "npx",
53
+ "args": ["-y", "@snippkit/mcp-server"],
54
+ "env": {
55
+ "SNIPPKIT_API_KEY": "sk_live_YOUR_API_KEY"
56
+ }
57
+ }
58
+ }
59
+ }
60
+ ```
61
+
62
+ ---
63
+
64
+ ## 🛠️ Provided Tools
65
+
66
+ | Tool | Description |
67
+ | :--- | :--- |
68
+ | `snippkit_search_skills` | Search public and team AI Skills in the SnippKit registry by keywords or agent compatibility. |
69
+ | `snippkit_get_skill` | Fetch complete `SKILL.md` instructions and procedural rules to guide agent execution. |
70
+ | `snippkit_search_snippets` | Search your private and public code snippets by title, tag, or language. |
71
+ | `snippkit_get_snippet` | Retrieve decrypted code from your SnippKit library. |
72
+ | `snippkit_save_snippet` | Save newly written or refactored code directly into SnippKit from the agent. |
73
+ | `snippkit_search_commands` | Search verified shell scripts and DevOps command runbooks. |
74
+ | `snippkit_get_command` | Retrieve shell command scripts. |
75
+
76
+ ---
77
+
78
+ ## 📚 Resources & Prompts
79
+
80
+ - **Resources:**
81
+ - `snippkit://skills/{slug}`: Read a skill's full instructions as a direct context resource.
82
+ - `snippkit://snippets/{id}`: Read a snippet as a direct context resource.
83
+ - **Prompts:**
84
+ - `use-skill`: Injects a named SnippKit skill into the agent's context window.
85
+ - `save-solution`: Prompts the agent to format and persist code to SnippKit.
86
+
87
+ ---
88
+
89
+ ## 🔐 Authentication
90
+
91
+ Generate your API key from [SnippKit Dashboard > Settings > Developer Keys](https://snippkit.com/settings/developer).
92
+
93
+ The server checks credentials in this order:
94
+ 1. `SNIPPKIT_API_KEY` environment variable.
95
+ 2. Command-line flag `--api-key <key>`.
96
+ 3. Local `snix` CLI vault (if you have already logged in via `snix login`).
97
+
98
+ ---
99
+
100
+ ## 📄 License
101
+
102
+ MIT © [SnippKit](https://snippkit.com)
2
103
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
package/bin/index.js ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { startServer } from '../src/index.js';
4
+
5
+ startServer().catch((error) => {
6
+ console.error('[snippkit-mcp] Fatal server startup error:', error);
7
+ process.exit(1);
8
+ });
9
+
package/package.json CHANGED
@@ -1,6 +1,54 @@
1
1
  {
2
2
  "name": "@snippkit/mcp-server",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "1.0.0",
4
+ "description": "Official Model Context Protocol (MCP) Server for SnippKit - Seamless integration with AI agents (Cursor, Claude Desktop, Antigravity, Windsurf) for Agent Skills, Code Snippets, and Shell Commands.",
5
+ "main": "src/index.js",
6
+ "type": "module",
7
+ "bin": {
8
+ "snippkit-mcp": "bin/index.js",
9
+ "mcp-server": "bin/index.js"
10
+ },
11
+ "files": [
12
+ "bin",
13
+ "src",
14
+ "README.md",
15
+ "LICENSE"
16
+ ],
17
+ "publishConfig": {
18
+ "access": "public"
19
+ },
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "git+https://github.com/Yaseen549/snippkit-mcp-server.git"
23
+ },
24
+ "homepage": "https://snippkit.com",
25
+ "bugs": {
26
+ "url": "https://github.com/Yaseen549/snippkit-mcp-server/issues"
27
+ },
28
+ "scripts": {
29
+ "start": "node bin/index.js",
30
+ "dev": "node bin/index.js"
31
+ },
32
+ "keywords": [
33
+ "mcp",
34
+ "model-context-protocol",
35
+ "snippkit",
36
+ "ai-agent",
37
+ "ai-skills",
38
+ "cursor",
39
+ "claude-desktop",
40
+ "antigravity",
41
+ "windsurf"
42
+ ],
43
+ "author": "SnippKit",
44
+ "license": "MIT",
45
+ "dependencies": {
46
+ "@modelcontextprotocol/sdk": "^1.6.0",
47
+ "axios": "^1.7.9",
48
+ "conf": "^12.0.0",
49
+ "zod": "^3.24.2"
50
+ },
51
+ "engines": {
52
+ "node": ">=18.0.0"
53
+ }
54
+ }
package/src/client.js ADDED
@@ -0,0 +1,141 @@
1
+ import axios from 'axios';
2
+ import { getConfig } from './config.js';
3
+
4
+ export class SnippKitClient {
5
+ constructor() {
6
+ const { apiKey, apiUrl } = getConfig();
7
+ this.apiKey = apiKey;
8
+ this.apiUrl = apiUrl;
9
+
10
+ this.http = axios.create({
11
+ baseURL: apiUrl,
12
+ headers: {
13
+ 'Content-Type': 'application/json',
14
+ 'User-Agent': '@snippkit/mcp-server/1.0.0',
15
+ },
16
+ timeout: 15000,
17
+ });
18
+
19
+ this.http.interceptors.request.use((reqConfig) => {
20
+ // Re-fetch current config in case updated dynamically
21
+ const current = getConfig();
22
+ if (current.apiKey && !reqConfig.headers['Authorization']) {
23
+ reqConfig.headers['Authorization'] = `Bearer ${current.apiKey}`;
24
+ }
25
+ return reqConfig;
26
+ });
27
+ }
28
+
29
+ // --------------------------------------------------------------------------
30
+ // SKILLS
31
+ // --------------------------------------------------------------------------
32
+ async searchSkills({ query = '', agent = '', sort = 'installs', limit = 10 } = {}) {
33
+ const params = new URLSearchParams();
34
+ if (query) params.set('q', query);
35
+ if (agent && agent !== 'all') params.set('agent', agent);
36
+ if (sort) params.set('sort', sort);
37
+ if (limit) params.set('limit', String(limit));
38
+
39
+ const res = await this.http.get(`/api/skills/search?${params.toString()}`);
40
+ return res.data;
41
+ }
42
+
43
+ async getSkill({ slug, id, username } = {}) {
44
+ const target = id || slug;
45
+ if (!target) throw new Error('Skill slug or ID is required');
46
+
47
+ // First try /api/skills/read (full markdown and author details)
48
+ const params = new URLSearchParams();
49
+ if (id) params.set('id', id);
50
+ else if (slug) params.set('slug', slug);
51
+ if (username) params.set('username', username);
52
+
53
+ try {
54
+ const res = await this.http.get(`/api/skills/read?${params.toString()}`);
55
+ if (res.data && !res.data.error) {
56
+ return res.data;
57
+ }
58
+ } catch (err) {
59
+ // Fallback to /api/cli/run?type=skills
60
+ }
61
+
62
+ const fallbackRes = await this.http.get(`/api/cli/run?type=skills&id=${encodeURIComponent(target)}`);
63
+ return fallbackRes.data;
64
+ }
65
+
66
+ // --------------------------------------------------------------------------
67
+ // SNIPPETS
68
+ // --------------------------------------------------------------------------
69
+ async searchSnippets({ query = '' } = {}) {
70
+ if (!this.apiKey) {
71
+ throw new Error('SnippKit API Key is required to search private snippets. Set SNIPPKIT_API_KEY environment variable.');
72
+ }
73
+ const params = new URLSearchParams({ type: 'snippets' });
74
+ if (query) params.set('search', query);
75
+
76
+ const res = await this.http.get(`/api/cli/list?${params.toString()}`);
77
+ return res.data;
78
+ }
79
+
80
+ async getSnippet({ id, slug } = {}) {
81
+ const target = id || slug;
82
+ if (!target) throw new Error('Snippet ID or slug is required');
83
+
84
+ const res = await this.http.get(`/api/cli/run?type=snippets&id=${encodeURIComponent(target)}`);
85
+ return res.data;
86
+ }
87
+
88
+ async saveSnippet({ title, code, language = 'plaintext', description = '', is_public = false, tags = [] } = {}) {
89
+ if (!this.apiKey) {
90
+ throw new Error('SnippKit API Key is required to save snippets. Set SNIPPKIT_API_KEY environment variable.');
91
+ }
92
+ if (!title) throw new Error('Snippet title is required');
93
+ if (!code) throw new Error('Snippet code is required');
94
+
95
+ const payload = {
96
+ title,
97
+ code,
98
+ language: language.toLowerCase(),
99
+ description,
100
+ is_public: Boolean(is_public),
101
+ tags: Array.isArray(tags) ? tags : [],
102
+ };
103
+
104
+ const res = await this.http.post('/api/s/create', payload);
105
+ return res.data;
106
+ }
107
+
108
+ // --------------------------------------------------------------------------
109
+ // COMMANDS
110
+ // --------------------------------------------------------------------------
111
+ async searchCommands({ query = '' } = {}) {
112
+ if (!this.apiKey) {
113
+ throw new Error('SnippKit API Key is required to search commands. Set SNIPPKIT_API_KEY environment variable.');
114
+ }
115
+ const params = new URLSearchParams({ type: 'commands' });
116
+ if (query) params.set('search', query);
117
+
118
+ const res = await this.http.get(`/api/cli/list?${params.toString()}`);
119
+ return res.data;
120
+ }
121
+
122
+ async getCommand({ id, slug } = {}) {
123
+ const target = id || slug;
124
+ if (!target) throw new Error('Command ID or slug is required');
125
+
126
+ const res = await this.http.get(`/api/cli/run?type=commands&id=${encodeURIComponent(target)}`);
127
+ return res.data;
128
+ }
129
+
130
+ // --------------------------------------------------------------------------
131
+ // FOLDERS
132
+ // --------------------------------------------------------------------------
133
+ async listFolders() {
134
+ if (!this.apiKey) {
135
+ throw new Error('SnippKit API Key is required to list folders. Set SNIPPKIT_API_KEY environment variable.');
136
+ }
137
+ const res = await this.http.get('/api/cli/list?type=folders');
138
+ return res.data;
139
+ }
140
+ }
141
+
package/src/config.js ADDED
@@ -0,0 +1,90 @@
1
+ import Conf from 'conf';
2
+ import crypto from 'crypto';
3
+ import os from 'os';
4
+
5
+ // Parse command line arguments for --api-key and --api-url
6
+ function parseArgs() {
7
+ const args = process.argv.slice(2);
8
+ const result = {};
9
+ for (let i = 0; i < args.length; i++) {
10
+ const arg = args[i];
11
+ if (arg === '--api-key' || arg === '--key') {
12
+ result.apiKey = args[++i];
13
+ } else if (arg.startsWith('--api-key=')) {
14
+ result.apiKey = arg.split('=')[1];
15
+ } else if (arg === '--api-url' || arg === '--url') {
16
+ result.apiUrl = args[++i];
17
+ } else if (arg.startsWith('--api-url=')) {
18
+ result.apiUrl = arg.split('=')[1];
19
+ }
20
+ }
21
+ return result;
22
+ }
23
+
24
+ const cliArgs = parseArgs();
25
+
26
+ // Helper to decrypt key from snix CLI vault if present
27
+ function getDerivedKey() {
28
+ let user = 'user';
29
+ try {
30
+ user = process.env.USERNAME || process.env.USER || os.userInfo()?.username || 'user';
31
+ } catch (e) {
32
+ user = process.env.USERNAME || process.env.USER || 'user';
33
+ }
34
+ let host = 'localhost';
35
+ try {
36
+ host = os.hostname() || process.env.COMPUTERNAME || 'localhost';
37
+ } catch (e) {
38
+ host = process.env.COMPUTERNAME || 'localhost';
39
+ }
40
+ const cleanUser = String(user).toLowerCase().trim();
41
+ const cleanHost = String(host).toLowerCase().trim();
42
+ const machineSecret = `${cleanUser}_${cleanHost}_SNIPPKIT_CLI_V2_VAULT_SECRET`;
43
+ return crypto.pbkdf2Sync(machineSecret, 'snippkit_v2_salt_secure_vault', 100000, 32, 'sha256');
44
+ }
45
+
46
+ function readCliVaultKey() {
47
+ try {
48
+ const cliConfig = new Conf({
49
+ projectName: 'snippkit-cli',
50
+ configFileMode: 0o600
51
+ });
52
+
53
+ const vaultData = cliConfig.get('secureVault');
54
+ if (!vaultData || !vaultData.iv || !vaultData.authTag || !vaultData.ciphertext) {
55
+ // Fallback to legacy plaintext if any
56
+ const legacyKey = cliConfig.get('apiKey');
57
+ return typeof legacyKey === 'string' ? legacyKey.trim() : null;
58
+ }
59
+
60
+ const key = getDerivedKey();
61
+ const iv = Buffer.from(vaultData.iv, 'hex');
62
+ const authTag = Buffer.from(vaultData.authTag, 'hex');
63
+ const decipher = crypto.createDecipheriv('aes-256-gcm', key, iv);
64
+ decipher.setAuthTag(authTag);
65
+ let decrypted = decipher.update(vaultData.ciphertext, 'hex', 'utf8');
66
+ decrypted += decipher.final('utf8');
67
+ return decrypted || null;
68
+ } catch {
69
+ return null;
70
+ }
71
+ }
72
+
73
+ export function getConfig() {
74
+ const apiKey =
75
+ cliArgs.apiKey ||
76
+ process.env.SNIPPKIT_API_KEY ||
77
+ process.env.SNIPPKIT_KEY ||
78
+ readCliVaultKey() ||
79
+ null;
80
+
81
+ const apiUrl = (
82
+ cliArgs.apiUrl ||
83
+ process.env.SNIPPKIT_API_URL ||
84
+ process.env.SNIPPKIT_BASE_URL ||
85
+ 'https://snippkit.com'
86
+ ).replace(/\/+$/, '');
87
+
88
+ return { apiKey, apiUrl };
89
+ }
90
+
package/src/index.js ADDED
@@ -0,0 +1,58 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
3
+
4
+ import { SnippKitClient } from './client.js';
5
+ import { getConfig } from './config.js';
6
+ import { registerSkillTools } from './tools/skills.js';
7
+ import { registerSnippetTools } from './tools/snippets.js';
8
+ import { registerCommandTools } from './tools/commands.js';
9
+ import { registerResources } from './resources/index.js';
10
+ import { registerPrompts } from './prompts/index.js';
11
+
12
+ export function createServer() {
13
+ const config = getConfig();
14
+ const client = new SnippKitClient();
15
+
16
+ const server = new McpServer(
17
+ {
18
+ name: 'snippkit',
19
+ version: '1.0.0',
20
+ },
21
+ {
22
+ capabilities: {
23
+ tools: {},
24
+ resources: {},
25
+ prompts: {},
26
+ logging: {},
27
+ },
28
+ }
29
+ );
30
+
31
+ // Register all capabilities
32
+ registerSkillTools(server, client);
33
+ registerSnippetTools(server, client);
34
+ registerCommandTools(server, client);
35
+ registerResources(server, client);
36
+ registerPrompts(server, client);
37
+
38
+ return { server, client, config };
39
+ }
40
+
41
+ export async function startServer() {
42
+ const { server, config } = createServer();
43
+ const transport = new StdioServerTransport();
44
+
45
+ // Log diagnostic info to stderr (stdout is strictly reserved for JSON-RPC MCP messages!)
46
+ console.error('[snippkit-mcp] Initializing SnippKit MCP Server v1.0.0...');
47
+ console.error(`[snippkit-mcp] API Endpoint: ${config.apiUrl}`);
48
+ if (config.apiKey) {
49
+ const masked = `${config.apiKey.slice(0, 7)}...${config.apiKey.slice(-4)}`;
50
+ console.error(`[snippkit-mcp] Authentication: Configured (${masked})`);
51
+ } else {
52
+ console.error('[snippkit-mcp] Authentication: Public access only (Set SNIPPKIT_API_KEY for private snippets)');
53
+ }
54
+
55
+ await server.connect(transport);
56
+ console.error('[snippkit-mcp] Connected over stdio transport. Listening for agent requests.');
57
+ }
58
+
@@ -0,0 +1,77 @@
1
+ import { z } from 'zod';
2
+
3
+ export function registerPrompts(server, client) {
4
+ // 1. Prompt: use-skill
5
+ server.prompt(
6
+ 'use-skill',
7
+ 'Load and apply a procedural AI skill from SnippKit into your context window.',
8
+ {
9
+ slug: z.string().describe("The slug or identifier of the skill to apply (e.g. 'hello-world')"),
10
+ },
11
+ async ({ slug }) => {
12
+ try {
13
+ const skill = await client.getSkill({ slug });
14
+ const title = skill?.title || slug;
15
+ const content = skill?.content || skill?.description || 'No instructions found.';
16
+
17
+ return {
18
+ messages: [
19
+ {
20
+ role: 'user',
21
+ content: {
22
+ type: 'text',
23
+ text: `I want you to follow the rules and procedures from the SnippKit Skill "${title}".
24
+
25
+ Instructions from SKILL.md:
26
+ ================================================================================
27
+ ${content}
28
+ ================================================================================
29
+
30
+ Please confirm you have read and understood these instructions before proceeding.`,
31
+ },
32
+ },
33
+ ],
34
+ };
35
+ } catch (err) {
36
+ return {
37
+ messages: [
38
+ {
39
+ role: 'user',
40
+ content: {
41
+ type: 'text',
42
+ text: `Please fetch and apply the skill "${slug}" from SnippKit. (Auto-fetch failed: ${err.message})`,
43
+ },
44
+ },
45
+ ],
46
+ };
47
+ }
48
+ }
49
+ );
50
+
51
+ // 2. Prompt: save-solution
52
+ server.prompt(
53
+ 'save-solution',
54
+ 'Prompt the AI agent to review the code just written and save it as a reusable snippet in SnippKit.',
55
+ {
56
+ title: z.string().optional().describe('Proposed title for the snippet'),
57
+ language: z.string().optional().describe('Programming language'),
58
+ },
59
+ async ({ title = '', language = '' }) => {
60
+ return {
61
+ messages: [
62
+ {
63
+ role: 'user',
64
+ content: {
65
+ type: 'text',
66
+ text: `Please review the code we just developed. Format it cleanly, add comments explaining how to use it, and save it to my SnippKit library using \`snippkit_save_snippet\` with:
67
+ - Title: ${title || 'Meaningful, descriptive title'}
68
+ - Language: ${language || 'appropriate programming language'}
69
+ - Description: summary of why this code works and when to reuse it`,
70
+ },
71
+ },
72
+ ],
73
+ };
74
+ }
75
+ );
76
+ }
77
+
@@ -0,0 +1,66 @@
1
+ import { ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+
3
+ export function registerResources(server, client) {
4
+ // 1. Skill Resource: snippkit://skills/{slug}
5
+ server.resource(
6
+ 'skill',
7
+ new ResourceTemplate('snippkit://skills/{slug}', { list: undefined }),
8
+ async (uri, { slug }) => {
9
+ try {
10
+ const skill = await client.getSkill({ slug });
11
+ const content = skill?.content || skill?.description || 'No content found';
12
+ return {
13
+ contents: [
14
+ {
15
+ uri: uri.href,
16
+ text: content,
17
+ mimeType: 'text/markdown',
18
+ },
19
+ ],
20
+ };
21
+ } catch (err) {
22
+ return {
23
+ contents: [
24
+ {
25
+ uri: uri.href,
26
+ text: `Error loading skill "${slug}": ${err.message}`,
27
+ mimeType: 'text/plain',
28
+ },
29
+ ],
30
+ };
31
+ }
32
+ }
33
+ );
34
+
35
+ // 2. Snippet Resource: snippkit://snippets/{id}
36
+ server.resource(
37
+ 'snippet',
38
+ new ResourceTemplate('snippkit://snippets/{id}', { list: undefined }),
39
+ async (uri, { id }) => {
40
+ try {
41
+ const snip = await client.getSnippet({ id });
42
+ const code = snip?.code || snip?.snippet?.code || '';
43
+ return {
44
+ contents: [
45
+ {
46
+ uri: uri.href,
47
+ text: code,
48
+ mimeType: 'text/plain',
49
+ },
50
+ ],
51
+ };
52
+ } catch (err) {
53
+ return {
54
+ contents: [
55
+ {
56
+ uri: uri.href,
57
+ text: `Error loading snippet "${id}": ${err.message}`,
58
+ mimeType: 'text/plain',
59
+ },
60
+ ],
61
+ };
62
+ }
63
+ }
64
+ );
65
+ }
66
+
@@ -0,0 +1,90 @@
1
+ import { z } from 'zod';
2
+
3
+ export function registerCommandTools(server, client) {
4
+ // 1. Search Commands
5
+ server.tool(
6
+ 'snippkit_search_commands',
7
+ 'Search verified shell commands, deployment scripts, and terminal runbooks stored in your SnippKit library.',
8
+ {
9
+ query: z.string().optional().describe("Search keywords, e.g. 'docker', 'git', 'kubectl', 'npm'"),
10
+ },
11
+ async ({ query = '' }) => {
12
+ try {
13
+ const commands = await client.searchCommands({ query });
14
+ if (!Array.isArray(commands) || commands.length === 0) {
15
+ return {
16
+ content: [
17
+ {
18
+ type: 'text',
19
+ text: `No shell commands found matching "${query}".`,
20
+ },
21
+ ],
22
+ };
23
+ }
24
+
25
+ const formatted = commands.map((c) => {
26
+ const lines = Array.isArray(c.commands) ? c.commands : [];
27
+ return `- **${c.title}**
28
+ ID: \`${c.id}\` | Slug: \`${c.slug || 'n/a'}\`
29
+ Steps: ${c.command_count || lines.length}
30
+ Preview: \`${lines[0] || 'N/A'}\`
31
+ `;
32
+ }).join('\n');
33
+
34
+ return {
35
+ content: [
36
+ {
37
+ type: 'text',
38
+ text: `Found ${commands.length} command runbook(s) in your SnippKit library:\n\n${formatted}\n\n*Tip: Call \`snippkit_get_command\` with an ID to inspect the full script.*`,
39
+ },
40
+ ],
41
+ };
42
+ } catch (err) {
43
+ return {
44
+ isError: true,
45
+ content: [{ type: 'text', text: `Failed to search commands: ${err.message}` }],
46
+ };
47
+ }
48
+ }
49
+ );
50
+
51
+ // 2. Get Command
52
+ server.tool(
53
+ 'snippkit_get_command',
54
+ 'Retrieve the shell commands/scripts from a runbook in SnippKit by ID or slug.',
55
+ {
56
+ id: z.string().describe('Command runbook UUID or slug'),
57
+ },
58
+ async ({ id }) => {
59
+ try {
60
+ const result = await client.getCommand({ id });
61
+ if (!result) {
62
+ return {
63
+ isError: true,
64
+ content: [{ type: 'text', text: `Command "${id}" not found.` }],
65
+ };
66
+ }
67
+
68
+ const title = result.title || 'Command Runbook';
69
+ const lines = Array.isArray(result.commands)
70
+ ? result.commands.join('\n')
71
+ : result.command || result.commands || '';
72
+
73
+ return {
74
+ content: [
75
+ {
76
+ type: 'text',
77
+ text: `### Command: ${title}\n\n\`\`\`bash\n${lines}\n\`\`\``,
78
+ },
79
+ ],
80
+ };
81
+ } catch (err) {
82
+ return {
83
+ isError: true,
84
+ content: [{ type: 'text', text: `Failed to fetch command "${id}": ${err.message}` }],
85
+ };
86
+ }
87
+ }
88
+ );
89
+ }
90
+
@@ -0,0 +1,109 @@
1
+ import { z } from 'zod';
2
+
3
+ export function registerSkillTools(server, client) {
4
+ // 1. Search Skills
5
+ server.tool(
6
+ 'snippkit_search_skills',
7
+ 'Search the SnippKit AI Skills Registry for agent capabilities, procedural rules, and autonomous instructions.',
8
+ {
9
+ query: z.string().optional().describe("Search keywords, e.g. 'supabase', 'nextjs', 'docker', 'auth'"),
10
+ agent: z
11
+ .enum(['all', 'antigravity', 'cursor', 'claude-code', 'windsurf', 'chatgpt', 'gemini-cli'])
12
+ .optional()
13
+ .default('all')
14
+ .describe('Filter by AI agent compatibility'),
15
+ limit: z.number().int().min(1).max(25).optional().default(10).describe('Max skills to return'),
16
+ },
17
+ async ({ query = '', agent = 'all', limit = 10 }) => {
18
+ try {
19
+ const skills = await client.searchSkills({ query, agent, limit });
20
+ if (!Array.isArray(skills) || skills.length === 0) {
21
+ return {
22
+ content: [
23
+ {
24
+ type: 'text',
25
+ text: `No skills found in SnippKit registry matching "${query}". Try searching with broader terms.`,
26
+ },
27
+ ],
28
+ };
29
+ }
30
+
31
+ const formatted = skills.map((s) => {
32
+ const author = s.username || s.author?.username || 'developer';
33
+ const tags = Array.isArray(s.tags) ? s.tags.map((t) => `#${t}`).join(' ') : '';
34
+ return `### ${s.title} (v${s.version || '1.0.0'})
35
+ - **Slug / ID**: \`@${author}/${s.slug || s.id}\`
36
+ - **Description**: ${s.description || 'No description provided'}
37
+ - **Compatibility**: ${(s.compatibility || []).join(', ') || 'universal'}
38
+ - **Downloads**: ${s.downloads || 0} installs | **Stars**: ${s.stars || 0}
39
+ - **Tags**: ${tags || 'none'}
40
+ - **Install CLI**: \`snix skill add @${author}/${s.slug || s.id}\`
41
+ `;
42
+ }).join('\n---\n\n');
43
+
44
+ return {
45
+ content: [
46
+ {
47
+ type: 'text',
48
+ text: `Found ${skills.length} matching skill(s) in SnippKit Registry:\n\n${formatted}\n\n*Tip: Call \`snippkit_get_skill\` with a slug to fetch the full instructions.*`,
49
+ },
50
+ ],
51
+ };
52
+ } catch (err) {
53
+ return {
54
+ isError: true,
55
+ content: [{ type: 'text', text: `Failed to search skills: ${err.message}` }],
56
+ };
57
+ }
58
+ }
59
+ );
60
+
61
+ // 2. Get Skill
62
+ server.tool(
63
+ 'snippkit_get_skill',
64
+ 'Fetch the complete SKILL.md instructions and procedural rules for an AI skill from SnippKit. Use this to guide your execution with expert domain knowledge.',
65
+ {
66
+ slug: z.string().describe("The slug or identifier of the skill (e.g. 'hello-world' or '@username/slug')"),
67
+ username: z.string().optional().describe('Author username if known'),
68
+ },
69
+ async ({ slug, username }) => {
70
+ try {
71
+ const skill = await client.getSkill({ slug, username });
72
+ if (!skill) {
73
+ return {
74
+ isError: true,
75
+ content: [{ type: 'text', text: `Skill "${slug}" was not found or is private.` }],
76
+ };
77
+ }
78
+
79
+ const author = skill.username || skill.author?.username || username || 'developer';
80
+ const header = `# Skill: ${skill.title} (v${skill.version || '1.0.0'})
81
+ Author: @${author}
82
+ Slug: ${skill.slug || slug}
83
+ Compatibility: ${(skill.compatibility || []).join(', ') || 'universal'}
84
+ Description: ${skill.description || 'N/A'}
85
+
86
+ ================================================================================
87
+ SKILL.md INSTRUCTIONS
88
+ ================================================================================
89
+ `;
90
+ const content = skill.content || skill.description || 'No instruction body found for this skill.';
91
+
92
+ return {
93
+ content: [
94
+ {
95
+ type: 'text',
96
+ text: `${header}\n${content}`,
97
+ },
98
+ ],
99
+ };
100
+ } catch (err) {
101
+ return {
102
+ isError: true,
103
+ content: [{ type: 'text', text: `Failed to fetch skill "${slug}": ${err.message}` }],
104
+ };
105
+ }
106
+ }
107
+ );
108
+ }
109
+
@@ -0,0 +1,131 @@
1
+ import { z } from 'zod';
2
+
3
+ export function registerSnippetTools(server, client) {
4
+ // 1. Search Snippets
5
+ server.tool(
6
+ 'snippkit_search_snippets',
7
+ 'Search your personal and bookmarked code snippets library in SnippKit by title, language, or keyword.',
8
+ {
9
+ query: z.string().optional().describe("Search keywords or language, e.g. 'auth', 'jwt', 'python', 'react'"),
10
+ },
11
+ async ({ query = '' }) => {
12
+ try {
13
+ const snippets = await client.searchSnippets({ query });
14
+ if (!Array.isArray(snippets) || snippets.length === 0) {
15
+ return {
16
+ content: [
17
+ {
18
+ type: 'text',
19
+ text: `No snippets found matching "${query}".`,
20
+ },
21
+ ],
22
+ };
23
+ }
24
+
25
+ const formatted = snippets.map((s) => {
26
+ return `- **${s.title}** (\`${s.language || 'plaintext'}\`)
27
+ ID: \`${s.id}\` | Slug: \`${s.slug || 'n/a'}\`
28
+ Public: ${s.is_public ? 'Yes' : 'No (Encrypted)'} | Size: ${s.size_in_bytes || 0} bytes
29
+ `;
30
+ }).join('\n');
31
+
32
+ return {
33
+ content: [
34
+ {
35
+ type: 'text',
36
+ text: `Found ${snippets.length} snippet(s) in your SnippKit library:\n\n${formatted}\n\n*Tip: Call \`snippkit_get_snippet\` with an ID to read the full code.*`,
37
+ },
38
+ ],
39
+ };
40
+ } catch (err) {
41
+ return {
42
+ isError: true,
43
+ content: [{ type: 'text', text: `Failed to search snippets: ${err.message}` }],
44
+ };
45
+ }
46
+ }
47
+ );
48
+
49
+ // 2. Get Snippet
50
+ server.tool(
51
+ 'snippkit_get_snippet',
52
+ 'Retrieve the decrypted source code of a specific code snippet from SnippKit by ID or slug.',
53
+ {
54
+ id: z.string().describe('Snippet UUID or slug'),
55
+ },
56
+ async ({ id }) => {
57
+ try {
58
+ const result = await client.getSnippet({ id });
59
+ if (!result) {
60
+ return {
61
+ isError: true,
62
+ content: [{ type: 'text', text: `Snippet "${id}" not found.` }],
63
+ };
64
+ }
65
+
66
+ const title = result.title || 'Untitled Snippet';
67
+ const lang = result.language || 'plaintext';
68
+ const code = result.code || result.snippet?.code || '';
69
+
70
+ return {
71
+ content: [
72
+ {
73
+ type: 'text',
74
+ text: `### Snippet: ${title} (${lang})\n\n\`\`\`${lang}\n${code}\n\`\`\``,
75
+ },
76
+ ],
77
+ };
78
+ } catch (err) {
79
+ return {
80
+ isError: true,
81
+ content: [{ type: 'text', text: `Failed to fetch snippet "${id}": ${err.message}` }],
82
+ };
83
+ }
84
+ }
85
+ );
86
+
87
+ // 3. Save Snippet
88
+ server.tool(
89
+ 'snippkit_save_snippet',
90
+ 'Save newly generated or refactored code directly into your SnippKit library. Persists solutions across your projects and environments.',
91
+ {
92
+ title: z.string().describe('Clear, descriptive title for the snippet'),
93
+ code: z.string().describe('The source code to save'),
94
+ language: z.string().optional().default('plaintext').describe("Programming language (e.g. 'typescript', 'python', 'sql', 'bash')"),
95
+ description: z.string().optional().describe('Brief explanation of what this code does and when to use it'),
96
+ is_public: z.boolean().optional().default(false).describe('Set to true for public snippet, false for zero-knowledge encrypted private snippet'),
97
+ tags: z.array(z.string()).optional().describe('Array of tags for searching'),
98
+ },
99
+ async ({ title, code, language = 'plaintext', description = '', is_public = false, tags = [] }) => {
100
+ try {
101
+ const saved = await client.saveSnippet({
102
+ title,
103
+ code,
104
+ language,
105
+ description,
106
+ is_public,
107
+ tags,
108
+ });
109
+
110
+ const id = saved.id || saved.snippet?.id || 'created';
111
+ const slug = saved.slug || saved.snippet?.slug;
112
+ const link = slug ? `https://snippkit.com/s/${slug}` : `https://snippkit.com/s/${id}`;
113
+
114
+ return {
115
+ content: [
116
+ {
117
+ type: 'text',
118
+ text: `Successfully saved snippet to SnippKit!\n- **Title**: ${title}\n- **Language**: ${language}\n- **Access**: ${is_public ? 'Public' : 'Private (Encrypted)'}\n- **Snippet ID**: \`${id}\`\n- **Web URL**: ${link}`,
119
+ },
120
+ ],
121
+ };
122
+ } catch (err) {
123
+ return {
124
+ isError: true,
125
+ content: [{ type: 'text', text: `Failed to save snippet to SnippKit: ${err.message}` }],
126
+ };
127
+ }
128
+ }
129
+ );
130
+ }
131
+