@lifeaitools/rdc-skills 0.24.21 → 0.24.23

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rdc",
3
- "version": "0.24.21",
3
+ "version": "0.24.23",
4
4
  "description": "RDC typed-agent dispatch skill suite for Claude Code — plan, build, review, overnight unattended builds with work-item tracking and TDD enforcement.",
5
5
  "author": {
6
6
  "name": "LIFEAI",
@@ -125,6 +125,36 @@ function textResult(text) {
125
125
  return { content: [{ type: 'text', text }] };
126
126
  }
127
127
 
128
+ function directMcpUsage(message) {
129
+ return {
130
+ jsonrpc: '2.0',
131
+ error: {
132
+ code: -32000,
133
+ message,
134
+ help: {
135
+ endpoint: '/mcp',
136
+ method: 'POST',
137
+ headers: {
138
+ 'Content-Type': 'application/json',
139
+ Accept: 'application/json, text/event-stream',
140
+ },
141
+ tools: ['rdc_skill_list', 'rdc_skill_search', 'rdc_skill_get'],
142
+ response: 'Streamable HTTP returns Server-Sent Events. Parse the JSON-RPC envelope from each data: line; tool text is at result.content[0].text.',
143
+ curl: `curl -s -X POST https://rdc-skills.regendevcorp.com/mcp \\
144
+ -H 'Content-Type: application/json' \\
145
+ -H 'Accept: application/json, text/event-stream' \\
146
+ -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"rdc_skill_list","arguments":{}}}'`,
147
+ },
148
+ },
149
+ id: null,
150
+ };
151
+ }
152
+
153
+ function acceptsStreamableHttp(req) {
154
+ const accept = String(req.headers.accept || '');
155
+ return accept.includes('application/json') && accept.includes('text/event-stream');
156
+ }
157
+
128
158
  function buildMcpServer() {
129
159
  const srv = new McpServer({ name: 'rdc-skills', version: pkgVersion() });
130
160
 
@@ -225,6 +255,10 @@ function startHttp() {
225
255
  // POST /mcp — stateless StreamableHTTP transport, no Authorization required.
226
256
  app.post('/mcp', async (req, res) => {
227
257
  try {
258
+ if (!acceptsStreamableHttp(req)) {
259
+ res.status(406).json(directMcpUsage('MCP Streamable HTTP requires Accept: application/json, text/event-stream'));
260
+ return;
261
+ }
228
262
  const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined });
229
263
  const srv = buildMcpServer();
230
264
  await srv.connect(transport);
@@ -246,11 +280,7 @@ function startHttp() {
246
280
  });
247
281
 
248
282
  app.get('/mcp', (_req, res) => {
249
- res.status(405).json({
250
- jsonrpc: '2.0',
251
- error: { code: -32000, message: 'Use POST for MCP requests' },
252
- id: null,
253
- });
283
+ res.status(405).json(directMcpUsage('Use POST for MCP requests.'));
254
284
  });
255
285
 
256
286
  app.listen(PORT, () => {
package/git-sha.json CHANGED
@@ -1,3 +1,3 @@
1
1
  {
2
- "sha": "7e69f4c64ca6d8ef7f34491aeacc678521c5470e"
2
+ "sha": "118a743f830c5a240c3c3bc46f45adfcaed9d1e2"
3
3
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lifeaitools/rdc-skills",
3
- "version": "0.24.21",
3
+ "version": "0.24.23",
4
4
  "description": "RDC typed-agent dispatch skill suite for Claude Code - plan, build, review, overnight builds",
5
5
  "keywords": [
6
6
  "claude-code",
@@ -944,7 +944,9 @@ function listCommands() {
944
944
  if (!fs.existsSync(cmdsDir)) return;
945
945
  const files = fs.readdirSync(cmdsDir).filter(f => f.endsWith('.md')).sort();
946
946
  const plugin = readJson(path.join(repoRoot, '.claude-plugin', 'plugin.json'), {});
947
- const skillCount = Array.isArray(plugin.skills_meta) ? plugin.skills_meta.length : null;
947
+ const skillCount = Array.isArray(plugin.skills_meta)
948
+ ? plugin.skills_meta.length
949
+ : (plugin.skills_meta && typeof plugin.skills_meta === 'object' ? Object.keys(plugin.skills_meta).length : null);
948
950
  console.log('');
949
951
  if (skillCount !== null) {
950
952
  console.log(` \x1b[32mAvailable MCP skills (${skillCount}) and /rdc:* command shorthands (${files.length}):\x1b[0m`);
@@ -67,6 +67,16 @@ function curl(args, label) {
67
67
  return { status: res.status, stdout, stderr };
68
68
  }
69
69
 
70
+ function curlWithStatus(args, label) {
71
+ const res = curl([...args, '-w', '\n%{http_code}'], label);
72
+ const marker = res.stdout.match(/\n(\d{3})$/);
73
+ return {
74
+ ...res,
75
+ httpStatus: marker ? Number(marker[1]) : null,
76
+ body: marker ? res.stdout.slice(0, marker.index) : res.stdout,
77
+ };
78
+ }
79
+
70
80
  function postMcp(payload, label) {
71
81
  return curl([
72
82
  '-s',
@@ -191,7 +201,19 @@ async function main() {
191
201
  check('rdc_skill_get returns tool text path', getText.length > 500, `length ${getText.length}`);
192
202
  check('rdc_skill_get accepts visible rdc:build alias', /rdc:build/i.test(getText));
193
203
 
194
- const badAccept = curl([
204
+ const getMcp = curlWithStatus([
205
+ '-s',
206
+ `${TARGET}/mcp`,
207
+ ], 'get_mcp_help');
208
+ check('GET /mcp curl exits 0', getMcp.status === 0, getMcp.stderr);
209
+ check('GET /mcp returns 405', getMcp.httpStatus === 405, `status ${getMcp.httpStatus}`);
210
+ const getHelp = JSON.parse(getMcp.body);
211
+ check('GET /mcp explains POST method', getHelp.error?.help?.method === 'POST');
212
+ check('GET /mcp includes Streamable HTTP Accept header', getHelp.error?.help?.headers?.Accept === 'application/json, text/event-stream');
213
+ check('GET /mcp points to discovery tools', getHelp.error?.help?.tools?.includes('rdc_skill_list'));
214
+ check('GET /mcp explains result.content text path', /result\.content\[0\]\.text/.test(getHelp.error?.help?.response || ''));
215
+
216
+ const badAccept = curlWithStatus([
195
217
  '-s',
196
218
  '-X', 'POST',
197
219
  `${TARGET}/mcp`,
@@ -199,7 +221,10 @@ async function main() {
199
221
  '-d', JSON.stringify({ jsonrpc: '2.0', id: 5, method: 'tools/list' }),
200
222
  ], 'missing_accept_header');
201
223
  check('missing Accept header still returns inspectable response', badAccept.status === 0, badAccept.stderr);
202
- check('missing Accept header mentions Accept or returns JSON/SSE', /Accept|data:|jsonrpc|result/i.test(badAccept.stdout));
224
+ check('missing Accept header returns 406', badAccept.httpStatus === 406, `status ${badAccept.httpStatus}`);
225
+ const badAcceptHelp = JSON.parse(badAccept.body);
226
+ check('missing Accept header names required header', badAcceptHelp.error?.help?.headers?.Accept === 'application/json, text/event-stream');
227
+ check('missing Accept header includes curl example', /curl -s -X POST/.test(badAcceptHelp.error?.help?.curl || ''));
203
228
  } finally {
204
229
  if (proc) proc.kill();
205
230
  }
@@ -20,11 +20,21 @@ assert.equal(toml.status, 0, `${toml.stdout}\n${toml.stderr}`);
20
20
  assert.match(toml.stdout, /PASS/);
21
21
 
22
22
  const source = readFileSync(script, 'utf8');
23
+ const plugin = JSON.parse(readFileSync(join(REPO_ROOT, '.claude-plugin', 'plugin.json'), 'utf8'));
24
+ const skillCount = Array.isArray(plugin.skills_meta)
25
+ ? plugin.skills_meta.length
26
+ : Object.keys(plugin.skills_meta || {}).length;
27
+ assert.equal(skillCount, 29, 'test fixture should expose all 29 MCP skills from plugin skills_meta');
23
28
  assert.match(
24
29
  source,
25
30
  /Available MCP skills.*\/rdc:\* command shorthands/,
26
31
  'installer should distinguish the full MCP skill catalog from slash-command shorthands',
27
32
  );
33
+ assert.match(
34
+ source,
35
+ /Object\.keys\(plugin\.skills_meta\)\.length/,
36
+ 'installer should count object-shaped skills_meta manifests',
37
+ );
28
38
  assert.match(
29
39
  source,
30
40
  /rdc_skill_list, rdc_skill_search, and rdc_skill_get/,