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 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
+ }