jerry-skills-mcp 0.1.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 +21 -0
- package/README.md +193 -0
- package/mcp/jerry-mcp.mjs +204 -0
- package/package.json +12 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Vikash Vardhan
|
|
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
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
# JERRY
|
|
2
|
+
|
|
3
|
+
**The right skill for every prompt, picked automatically.**
|
|
4
|
+
|
|
5
|
+
JERRY searches a library of 2,300+ agent skills written by the community, finds the
|
|
6
|
+
ones that fit what you're working on, and hands them to your coding agent. You don't
|
|
7
|
+
browse, choose or install skills. You install JERRY once and keep working.
|
|
8
|
+
|
|
9
|
+
Works with **Claude Code**, **Codex** and **Gemini CLI**.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Install (2 minutes)
|
|
14
|
+
|
|
15
|
+
You need [Node.js 18+](https://nodejs.org) and `git`.
|
|
16
|
+
|
|
17
|
+
### 1. Clone
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
git clone https://github.com/vikasvardhanv/jerry-plugin.git
|
|
21
|
+
cd jerry-plugin
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### 2. Install for your agent
|
|
25
|
+
|
|
26
|
+
Run these **from inside the `jerry-plugin` folder**.
|
|
27
|
+
|
|
28
|
+
**Claude Code** — start `claude` in the folder, then type:
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
/plugin marketplace add ./
|
|
32
|
+
/plugin install jerry@jerry
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
**Codex** — in your terminal:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
codex plugin marketplace add ./
|
|
39
|
+
codex plugin add jerry@jerry
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
**Gemini CLI** — in your terminal:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
gemini extensions link .
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### 3. Restart your agent
|
|
49
|
+
|
|
50
|
+
Quit and reopen Claude Code, Codex or Gemini so it loads JERRY.
|
|
51
|
+
|
|
52
|
+
### 4. Check it works
|
|
53
|
+
|
|
54
|
+
Ask your agent something real, for example:
|
|
55
|
+
|
|
56
|
+
> Write a failing test first, then fix the off-by-one bug in this loop.
|
|
57
|
+
|
|
58
|
+
Your agent will get a short list of matching skills and load the one it needs. In
|
|
59
|
+
Claude Code, run `/jerry-skills` to see what JERRY offered and why.
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## Other ways to install
|
|
64
|
+
|
|
65
|
+
**Without cloning** (installs straight from GitHub):
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
# Claude Code (from a terminal)
|
|
69
|
+
claude plugin marketplace add vikasvardhanv/jerry-plugin && claude plugin install jerry@jerry
|
|
70
|
+
|
|
71
|
+
# Codex
|
|
72
|
+
codex plugin marketplace add vikasvardhanv/jerry-plugin && codex plugin add jerry@jerry
|
|
73
|
+
|
|
74
|
+
# Gemini CLI
|
|
75
|
+
gemini extensions install https://github.com/vikasvardhanv/jerry-plugin --consent
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
**Ask your agent to do it.** Paste this into Claude Code, Codex or Gemini:
|
|
79
|
+
|
|
80
|
+
> Install the JERRY plugin from github.com/vikasvardhanv/jerry-plugin
|
|
81
|
+
|
|
82
|
+
**Any other MCP client** (Cursor, Claude Desktop, ...) — add this to its MCP config:
|
|
83
|
+
|
|
84
|
+
```json
|
|
85
|
+
{
|
|
86
|
+
"mcpServers": {
|
|
87
|
+
"jerry-skills": { "command": "npx", "args": ["-y", "github:vikasvardhanv/jerry-plugin"] }
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## Update
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
cd jerry-plugin && git pull
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
then:
|
|
101
|
+
|
|
102
|
+
| Agent | Command |
|
|
103
|
+
|---|---|
|
|
104
|
+
| Claude Code | `claude plugin marketplace update jerry` then `claude plugin update jerry@jerry` |
|
|
105
|
+
| Codex | `codex plugin add jerry@jerry` (reinstalls from the updated folder) |
|
|
106
|
+
| Gemini CLI | nothing more: `link` always uses the folder as it is |
|
|
107
|
+
|
|
108
|
+
## Uninstall
|
|
109
|
+
|
|
110
|
+
| Agent | Command |
|
|
111
|
+
|---|---|
|
|
112
|
+
| Claude Code | `claude plugin uninstall jerry@jerry` and `claude plugin marketplace remove jerry` |
|
|
113
|
+
| Codex | `codex plugin remove jerry@jerry` and `codex plugin marketplace remove jerry` |
|
|
114
|
+
| Gemini CLI | `gemini extensions uninstall jerry` |
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## What happens on each prompt
|
|
119
|
+
|
|
120
|
+
1. JERRY searches the library by meaning, not keywords, and adds a short list of the
|
|
121
|
+
best matches to your agent's context: about 30 tokens per skill, names and
|
|
122
|
+
one-line summaries only.
|
|
123
|
+
2. If one fits, the agent loads that skill's full instructions. If none fit, it
|
|
124
|
+
ignores the list and carries on.
|
|
125
|
+
|
|
126
|
+
In Claude Code and Codex this runs automatically on every prompt. In Gemini CLI the
|
|
127
|
+
agent is instructed to search at the start of each task.
|
|
128
|
+
|
|
129
|
+
JERRY never blocks a prompt. If the service is slow or down, you just don't get a list.
|
|
130
|
+
|
|
131
|
+
## Why two steps
|
|
132
|
+
|
|
133
|
+
Pasting every plausible skill into every prompt wastes context and mostly goes unread.
|
|
134
|
+
A list costs a few dozen tokens; the agent then loads only the skill it will use.
|
|
135
|
+
|
|
136
|
+
It is also how skill authors get paid: showing up in the list is free, and **loading**
|
|
137
|
+
a skill credits its author.
|
|
138
|
+
|
|
139
|
+
## Write skills and get paid
|
|
140
|
+
|
|
141
|
+
Publish your own skills at **https://jerry.viktronconsulting.com/portal**:
|
|
142
|
+
|
|
143
|
+
1. Sign in and choose your author handle.
|
|
144
|
+
2. Paste or upload a `SKILL.md`. It's searchable as soon as you publish.
|
|
145
|
+
3. Connect Stripe to receive payouts.
|
|
146
|
+
4. See how often each skill is shown, loaded, and what it has earned.
|
|
147
|
+
|
|
148
|
+
Each load credits the skill's royalty (default $0.02). The same skill loaded twice in
|
|
149
|
+
one session within 24 hours is paid once.
|
|
150
|
+
|
|
151
|
+
If your skills are already in the library under your GitHub name, sign in with that
|
|
152
|
+
GitHub account to claim the royalties held for you.
|
|
153
|
+
|
|
154
|
+
## Commands (Claude Code)
|
|
155
|
+
|
|
156
|
+
- `/jerry-skills` — what was offered and loaded in this session, and why
|
|
157
|
+
- `/jerry-earnings [@author]` — impressions, loads and earnings per skill
|
|
158
|
+
|
|
159
|
+
## Configuration
|
|
160
|
+
|
|
161
|
+
Optional environment variables:
|
|
162
|
+
|
|
163
|
+
| Variable | Default | |
|
|
164
|
+
|---|---|---|
|
|
165
|
+
| `JERRY_API_URL` | `https://jerry.viktronconsulting.com` | API to query |
|
|
166
|
+
| `JERRY_MAX_SKILLS` | `3` | candidates listed per prompt |
|
|
167
|
+
| `JERRY_MIN_PROMPT_CHARS` | `24` | shorter prompts skip the search |
|
|
168
|
+
| `JERRY_TIMEOUT_MS` | `6000` | the prompt goes through unchanged past this |
|
|
169
|
+
|
|
170
|
+
## Troubleshooting
|
|
171
|
+
|
|
172
|
+
**No skills ever appear.** Check the service answers:
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
curl -s -X POST https://jerry.viktronconsulting.com/api/jerry/skills/index \
|
|
176
|
+
-H 'Content-Type: application/json' -d '{"query":"write a failing test first"}'
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
You should see `"mode":"semantic"` and a list of skills. Then check Node is 18 or newer
|
|
180
|
+
(`node --version`), and that you restarted your agent after installing.
|
|
181
|
+
|
|
182
|
+
**Only very long prompts get skills.** Prompts under 24 characters are skipped on
|
|
183
|
+
purpose. Set `JERRY_MIN_PROMPT_CHARS` lower to change that.
|
|
184
|
+
|
|
185
|
+
## Privacy
|
|
186
|
+
|
|
187
|
+
Your prompt is sent to the JERRY API to search the library. It is not stored: the
|
|
188
|
+
ledger keeps a SHA-256 hash so usage can be grouped for author payouts without keeping
|
|
189
|
+
what you typed.
|
|
190
|
+
|
|
191
|
+
## License
|
|
192
|
+
|
|
193
|
+
MIT
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* JERRY skills MCP server.
|
|
4
|
+
*
|
|
5
|
+
* Two tools, matching the two stages of retrieval:
|
|
6
|
+
* jerry_find_skills - cheap index of candidates, bills nothing
|
|
7
|
+
* jerry_load_skill - full instructions for one id, bills its author
|
|
8
|
+
*
|
|
9
|
+
* Deliberately dependency-free. MCP over stdio is JSON-RPC 2.0 with newline
|
|
10
|
+
* framing, which is about eighty lines; pulling in an SDK would make `npx
|
|
11
|
+
* jerry-skills-mcp` a package install on every cold start for no capability.
|
|
12
|
+
*
|
|
13
|
+
* Works in any MCP client: Claude Code, Codex, Gemini CLI, Claude Desktop, Cursor.
|
|
14
|
+
*/
|
|
15
|
+
import { createInterface } from 'node:readline';
|
|
16
|
+
|
|
17
|
+
const API = (process.env.JERRY_API_URL || 'https://jerry.viktronconsulting.com').replace(/\/$/, '');
|
|
18
|
+
const TIMEOUT_MS = Number(process.env.JERRY_TIMEOUT_MS || 10000);
|
|
19
|
+
const CLIENT = process.env.JERRY_CLIENT || 'mcp';
|
|
20
|
+
const SESSION = process.env.JERRY_SESSION_ID || `mcp-${process.pid}-${Date.now()}`;
|
|
21
|
+
|
|
22
|
+
const PROTOCOL_VERSION = '2024-11-05';
|
|
23
|
+
|
|
24
|
+
/** Remembers the last search so a load can be attributed back to it. */
|
|
25
|
+
const lastSearch = { queryHash: '', similarity: new Map() };
|
|
26
|
+
|
|
27
|
+
async function api(path, body) {
|
|
28
|
+
const controller = new AbortController();
|
|
29
|
+
const timer = setTimeout(() => controller.abort(), TIMEOUT_MS);
|
|
30
|
+
try {
|
|
31
|
+
const response = await fetch(`${API}${path}`, {
|
|
32
|
+
method: 'POST',
|
|
33
|
+
headers: {
|
|
34
|
+
'Content-Type': 'application/json',
|
|
35
|
+
...(process.env.JERRY_API_KEY
|
|
36
|
+
? { Authorization: `Bearer ${process.env.JERRY_API_KEY}` }
|
|
37
|
+
: {}),
|
|
38
|
+
},
|
|
39
|
+
signal: controller.signal,
|
|
40
|
+
body: JSON.stringify(body),
|
|
41
|
+
});
|
|
42
|
+
const text = await response.text();
|
|
43
|
+
let json;
|
|
44
|
+
try {
|
|
45
|
+
json = JSON.parse(text);
|
|
46
|
+
} catch {
|
|
47
|
+
throw new Error(`${path} returned non-JSON (HTTP ${response.status}): ${text.slice(0, 200)}`);
|
|
48
|
+
}
|
|
49
|
+
if (!response.ok) throw new Error(json.error || `${path} failed with HTTP ${response.status}`);
|
|
50
|
+
return json;
|
|
51
|
+
} finally {
|
|
52
|
+
clearTimeout(timer);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const TOOLS = [
|
|
57
|
+
{
|
|
58
|
+
name: 'jerry_find_skills',
|
|
59
|
+
description:
|
|
60
|
+
'Search the JERRY marketplace for community skills relevant to the current task. ' +
|
|
61
|
+
'Returns a short list of candidates with one-line summaries and their ids. ' +
|
|
62
|
+
'This is free and loads nothing into context beyond the list; call jerry_load_skill ' +
|
|
63
|
+
'to get the actual instructions for one you want to use.',
|
|
64
|
+
inputSchema: {
|
|
65
|
+
type: 'object',
|
|
66
|
+
properties: {
|
|
67
|
+
query: {
|
|
68
|
+
type: 'string',
|
|
69
|
+
description: 'What you are trying to do, in a sentence. More specific beats shorter.',
|
|
70
|
+
},
|
|
71
|
+
limit: { type: 'number', description: 'Max candidates to return (1-20, default 5).' },
|
|
72
|
+
},
|
|
73
|
+
required: ['query'],
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
name: 'jerry_load_skill',
|
|
78
|
+
description:
|
|
79
|
+
'Load the full instructions for one skill by id, from a prior jerry_find_skills result. ' +
|
|
80
|
+
'This credits a royalty to the skill author, so load a skill you intend to follow, ' +
|
|
81
|
+
'not one you are merely curious about.',
|
|
82
|
+
inputSchema: {
|
|
83
|
+
type: 'object',
|
|
84
|
+
properties: {
|
|
85
|
+
id: { type: 'string', description: 'Skill id exactly as jerry_find_skills returned it.' },
|
|
86
|
+
},
|
|
87
|
+
required: ['id'],
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
];
|
|
91
|
+
|
|
92
|
+
async function callTool(name, args) {
|
|
93
|
+
if (name === 'jerry_find_skills') {
|
|
94
|
+
const result = await api('/api/jerry/skills/index', {
|
|
95
|
+
query: args.query,
|
|
96
|
+
sessionId: SESSION,
|
|
97
|
+
client: CLIENT,
|
|
98
|
+
limit: args.limit ?? 5,
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
lastSearch.queryHash = result.queryHash || '';
|
|
102
|
+
lastSearch.similarity = new Map((result.skills || []).map((s) => [s.id, s.similarity]));
|
|
103
|
+
|
|
104
|
+
if (!result.skills?.length) {
|
|
105
|
+
return 'No marketplace skill matched this task. Proceed without one.';
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const lines = result.skills.map(
|
|
109
|
+
(s) => `${s.id} [${s.author}, match ${s.similarity}] ${s.name} - ${s.summary}`
|
|
110
|
+
);
|
|
111
|
+
const note =
|
|
112
|
+
result.mode === 'lexical-fallback'
|
|
113
|
+
? '\n\n(Vector index unreachable; this is a keyword fallback over built-in skills only.)'
|
|
114
|
+
: '';
|
|
115
|
+
return (
|
|
116
|
+
`${result.skills.length} candidate skill(s). Call jerry_load_skill with an id to use one:\n\n` +
|
|
117
|
+
lines.join('\n') +
|
|
118
|
+
note
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (name === 'jerry_load_skill') {
|
|
123
|
+
const result = await api('/api/jerry/skills/load', {
|
|
124
|
+
id: args.id,
|
|
125
|
+
sessionId: SESSION,
|
|
126
|
+
client: CLIENT,
|
|
127
|
+
queryHash: lastSearch.queryHash,
|
|
128
|
+
similarity: lastSearch.similarity.get(args.id) ?? null,
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
// Never claim a payment the server did not record.
|
|
132
|
+
const credit =
|
|
133
|
+
result.ledger === 'recorded'
|
|
134
|
+
? `Royalty $${result.royaltyCharged} credited to ${result.author}.`
|
|
135
|
+
: result.ledger === 'duplicate'
|
|
136
|
+
? `Already loaded in this session; ${result.author} was not charged again.`
|
|
137
|
+
: `Royalty $${result.royaltyPending} owed to ${result.author} but NOT recorded (ledger write failed).`;
|
|
138
|
+
|
|
139
|
+
return (
|
|
140
|
+
`${result.name} v${result.version} by ${result.author}\n` +
|
|
141
|
+
`${credit}\n\n${result.instructions}`
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function send(message) {
|
|
149
|
+
process.stdout.write(JSON.stringify(message) + '\n');
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function respond(id, result) {
|
|
153
|
+
send({ jsonrpc: '2.0', id, result });
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function fail(id, code, message) {
|
|
157
|
+
send({ jsonrpc: '2.0', id, error: { code, message } });
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
async function handle(request) {
|
|
161
|
+
const { id, method, params } = request;
|
|
162
|
+
|
|
163
|
+
// Notifications carry no id and must never be answered.
|
|
164
|
+
if (id === undefined || id === null) return;
|
|
165
|
+
|
|
166
|
+
switch (method) {
|
|
167
|
+
case 'initialize':
|
|
168
|
+
return respond(id, {
|
|
169
|
+
protocolVersion: params?.protocolVersion || PROTOCOL_VERSION,
|
|
170
|
+
capabilities: { tools: {} },
|
|
171
|
+
serverInfo: { name: 'jerry-skills', version: '0.1.0' },
|
|
172
|
+
});
|
|
173
|
+
case 'tools/list':
|
|
174
|
+
return respond(id, { tools: TOOLS });
|
|
175
|
+
case 'tools/call':
|
|
176
|
+
try {
|
|
177
|
+
const text = await callTool(params?.name, params?.arguments ?? {});
|
|
178
|
+
return respond(id, { content: [{ type: 'text', text }] });
|
|
179
|
+
} catch (error) {
|
|
180
|
+
// Reported as tool output, not as a protocol error: the model should see
|
|
181
|
+
// why the marketplace was unavailable and carry on without it.
|
|
182
|
+
return respond(id, {
|
|
183
|
+
content: [{ type: 'text', text: `JERRY unavailable: ${error.message}` }],
|
|
184
|
+
isError: true,
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
case 'ping':
|
|
188
|
+
return respond(id, {});
|
|
189
|
+
default:
|
|
190
|
+
return fail(id, -32601, `Method not found: ${method}`);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
createInterface({ input: process.stdin }).on('line', (line) => {
|
|
195
|
+
const trimmed = line.trim();
|
|
196
|
+
if (!trimmed) return;
|
|
197
|
+
let request;
|
|
198
|
+
try {
|
|
199
|
+
request = JSON.parse(trimmed);
|
|
200
|
+
} catch {
|
|
201
|
+
return fail(null, -32700, 'Parse error');
|
|
202
|
+
}
|
|
203
|
+
handle(request).catch((error) => fail(request?.id ?? null, -32603, error.message));
|
|
204
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "jerry-skills-mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "MCP server for the JERRY skill marketplace: semantic search over community agent skills, loaded on demand and metered so authors get paid.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": { "jerry-skills-mcp": "mcp/jerry-mcp.mjs" },
|
|
7
|
+
"files": ["mcp", "README.md"],
|
|
8
|
+
"engines": { "node": ">=18" },
|
|
9
|
+
"keywords": ["mcp", "model-context-protocol", "skills", "agent", "claude", "codex", "gemini"],
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"repository": { "type": "git", "url": "git+https://github.com/vikasvardhanv/jerry-plugin.git" }
|
|
12
|
+
}
|