recker 1.0.11-alpha.d342d95 → 1.0.11-alpha.e84cc95

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/cli/index.js CHANGED
@@ -329,13 +329,20 @@ ${pc.bold(pc.yellow('Examples:'))}
329
329
  program
330
330
  .command('mcp')
331
331
  .description('Start MCP server for AI agents to access Recker documentation')
332
- .option('-p, --port <number>', 'Server port', '3100')
332
+ .option('-t, --transport <mode>', 'Transport mode: stdio, http, sse', 'stdio')
333
+ .option('-p, --port <number>', 'Server port (for http/sse modes)', '3100')
333
334
  .option('-d, --docs <path>', 'Path to documentation folder')
334
335
  .option('--debug', 'Enable debug logging')
335
336
  .addHelpText('after', `
337
+ ${pc.bold(pc.yellow('Transport Modes:'))}
338
+ ${pc.cyan('stdio')} ${pc.gray('(default)')} For Claude Code and other CLI tools
339
+ ${pc.cyan('http')} Simple HTTP POST endpoint
340
+ ${pc.cyan('sse')} HTTP + Server-Sent Events for real-time notifications
341
+
336
342
  ${pc.bold(pc.yellow('Usage:'))}
337
- ${pc.green('$ rek mcp')} ${pc.gray('Start server on port 3100')}
338
- ${pc.green('$ rek mcp -p 8080')} ${pc.gray('Start on custom port')}
343
+ ${pc.green('$ rek mcp')} ${pc.gray('Start in stdio mode (for Claude Code)')}
344
+ ${pc.green('$ rek mcp -t http')} ${pc.gray('Start HTTP server on port 3100')}
345
+ ${pc.green('$ rek mcp -t sse -p 8080')} ${pc.gray('Start SSE server on custom port')}
339
346
  ${pc.green('$ rek mcp --debug')} ${pc.gray('Enable debug logging')}
340
347
 
341
348
  ${pc.bold(pc.yellow('Tools provided:'))}
@@ -354,19 +361,34 @@ ${pc.bold(pc.yellow('Claude Code config (~/.claude.json):'))}
354
361
  `)
355
362
  .action(async (options) => {
356
363
  const { MCPServer } = await import('../mcp/server.js');
364
+ const transport = options.transport;
357
365
  const server = new MCPServer({
366
+ transport,
358
367
  port: parseInt(options.port),
359
368
  docsPath: options.docs,
360
369
  debug: options.debug,
361
370
  });
371
+ if (transport === 'stdio') {
372
+ await server.start();
373
+ return;
374
+ }
362
375
  await server.start();
376
+ const endpoints = transport === 'sse'
377
+ ? `
378
+ │ POST / - JSON-RPC endpoint │
379
+ │ GET /sse - Server-Sent Events │
380
+ │ GET /health - Health check │`
381
+ : `
382
+ │ POST / - JSON-RPC endpoint │`;
363
383
  console.log(pc.green(`
364
384
  ┌─────────────────────────────────────────────┐
365
385
  │ ${pc.bold('Recker MCP Server')} │
366
386
  ├─────────────────────────────────────────────┤
367
- Endpoint: ${pc.cyan(`http://localhost:${options.port}`)}
387
+ Transport: ${pc.cyan(transport.padEnd(31))}│
388
+ │ Endpoint: ${pc.cyan(`http://localhost:${options.port}`.padEnd(32))}│
368
389
  │ Docs indexed: ${pc.yellow(String(server.getDocsCount()).padEnd(28))}│
369
- │ │
390
+ ├─────────────────────────────────────────────┤${endpoints}
391
+ ├─────────────────────────────────────────────┤
370
392
  │ Tools: │
371
393
  │ • ${pc.cyan('search_docs')} - Search documentation │
372
394
  │ • ${pc.cyan('get_doc')} - Get full doc content │
@@ -1,15 +1,21 @@
1
+ import type { JsonRpcRequest, JsonRpcResponse } from './types.js';
2
+ export type MCPTransportMode = 'stdio' | 'http' | 'sse';
1
3
  export interface MCPServerOptions {
2
4
  name?: string;
3
5
  version?: string;
4
6
  docsPath?: string;
5
7
  port?: number;
8
+ transport?: MCPTransportMode;
6
9
  debug?: boolean;
7
10
  }
8
11
  export declare class MCPServer {
9
12
  private options;
10
13
  private server?;
11
14
  private docsIndex;
15
+ private sseClients;
16
+ private initialized;
12
17
  constructor(options?: MCPServerOptions);
18
+ private log;
13
19
  private findDocsPath;
14
20
  private buildIndex;
15
21
  private walkDir;
@@ -20,11 +26,16 @@ export declare class MCPServer {
20
26
  private searchDocs;
21
27
  private extractSnippet;
22
28
  private getDoc;
23
- private handleRequest;
29
+ handleRequest(req: JsonRpcRequest): JsonRpcResponse;
30
+ private sendNotification;
31
+ private startStdio;
32
+ private startHttp;
33
+ private startSSE;
24
34
  start(): Promise<void>;
25
35
  stop(): Promise<void>;
26
36
  getPort(): number;
27
37
  getDocsCount(): number;
38
+ getTransport(): MCPTransportMode;
28
39
  }
29
40
  export declare function createMCPServer(options?: MCPServerOptions): MCPServer;
30
41
  //# sourceMappingURL=server.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/mcp/server.ts"],"names":[],"mappings":"AAcA,MAAM,WAAW,gBAAgB;IAC/B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAiBD,qBAAa,SAAS;IACpB,OAAO,CAAC,OAAO,CAA6B;IAC5C,OAAO,CAAC,MAAM,CAAC,CAAkC;IACjD,OAAO,CAAC,SAAS,CAAkB;gBAEvB,OAAO,GAAE,gBAAqB;IAY1C,OAAO,CAAC,YAAY;IAkBpB,OAAO,CAAC,UAAU;IAuClB,OAAO,CAAC,OAAO;IAyBf,OAAO,CAAC,YAAY;IAKpB,OAAO,CAAC,eAAe;IAyBvB,OAAO,CAAC,QAAQ;IAyChB,OAAO,CAAC,cAAc;IActB,OAAO,CAAC,UAAU;IA4DlB,OAAO,CAAC,cAAc;IAoBtB,OAAO,CAAC,MAAM;IAmCd,OAAO,CAAC,aAAa;IAiDf,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IA4DtB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAU3B,OAAO,IAAI,MAAM;IAIjB,YAAY,IAAI,MAAM;CAGvB;AAKD,wBAAgB,eAAe,CAAC,OAAO,CAAC,EAAE,gBAAgB,GAAG,SAAS,CAErE"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/mcp/server.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,cAAc,EACd,eAAe,EAMhB,MAAM,YAAY,CAAC;AAEpB,MAAM,MAAM,gBAAgB,GAAG,OAAO,GAAG,MAAM,GAAG,KAAK,CAAC;AAExD,MAAM,WAAW,gBAAgB;IAC/B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,gBAAgB,CAAC;IAC7B,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAsBD,qBAAa,SAAS;IACpB,OAAO,CAAC,OAAO,CAA6B;IAC5C,OAAO,CAAC,MAAM,CAAC,CAAkC;IACjD,OAAO,CAAC,SAAS,CAAkB;IACnC,OAAO,CAAC,UAAU,CAAkC;IACpD,OAAO,CAAC,WAAW,CAAS;gBAEhB,OAAO,GAAE,gBAAqB;IAa1C,OAAO,CAAC,GAAG;IAWX,OAAO,CAAC,YAAY;IAuBpB,OAAO,CAAC,UAAU;IAiClB,OAAO,CAAC,OAAO;IAyBf,OAAO,CAAC,YAAY;IAKpB,OAAO,CAAC,eAAe;IAqBvB,OAAO,CAAC,QAAQ;IAyChB,OAAO,CAAC,cAAc;IActB,OAAO,CAAC,UAAU;IA2DlB,OAAO,CAAC,cAAc;IAmBtB,OAAO,CAAC,MAAM;IAmCd,aAAa,CAAC,GAAG,EAAE,cAAc,GAAG,eAAe;IAkEnD,OAAO,CAAC,gBAAgB;YAUV,UAAU;YAqCV,SAAS;YAkDT,QAAQ;IA2FhB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAatB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAgB3B,OAAO,IAAI,MAAM;IAIjB,YAAY,IAAI,MAAM;IAItB,YAAY,IAAI,gBAAgB;CAGjC;AAoBD,wBAAgB,eAAe,CAAC,OAAO,CAAC,EAAE,gBAAgB,GAAG,SAAS,CAErE"}
@@ -1,27 +1,42 @@
1
1
  import { createServer } from 'http';
2
2
  import { readFileSync, readdirSync, statSync, existsSync } from 'fs';
3
3
  import { join, relative } from 'path';
4
+ import { createInterface } from 'readline';
4
5
  export class MCPServer {
5
6
  options;
6
7
  server;
7
8
  docsIndex = [];
9
+ sseClients = new Set();
10
+ initialized = false;
8
11
  constructor(options = {}) {
9
12
  this.options = {
10
13
  name: options.name || 'recker-docs',
11
14
  version: options.version || '1.0.0',
12
15
  docsPath: options.docsPath || this.findDocsPath(),
13
16
  port: options.port || 3100,
17
+ transport: options.transport || 'stdio',
14
18
  debug: options.debug || false,
15
19
  };
16
20
  this.buildIndex();
17
21
  }
22
+ log(message, data) {
23
+ if (this.options.debug) {
24
+ if (this.options.transport === 'stdio') {
25
+ console.error(`[MCP] ${message}`, data ? JSON.stringify(data) : '');
26
+ }
27
+ else {
28
+ console.log(`[MCP] ${message}`, data ? JSON.stringify(data) : '');
29
+ }
30
+ }
31
+ }
18
32
  findDocsPath() {
19
33
  const possiblePaths = [
20
34
  join(process.cwd(), 'docs'),
21
35
  join(process.cwd(), '..', 'docs'),
22
- join(__dirname, '..', '..', 'docs'),
23
- join(__dirname, '..', '..', '..', 'docs'),
24
36
  ];
37
+ if (typeof __dirname !== 'undefined') {
38
+ possiblePaths.push(join(__dirname, '..', '..', 'docs'), join(__dirname, '..', '..', '..', 'docs'));
39
+ }
25
40
  for (const p of possiblePaths) {
26
41
  if (existsSync(p)) {
27
42
  return p;
@@ -31,9 +46,7 @@ export class MCPServer {
31
46
  }
32
47
  buildIndex() {
33
48
  if (!existsSync(this.options.docsPath)) {
34
- if (this.options.debug) {
35
- console.log(`[MCP] Docs path not found: ${this.options.docsPath}`);
36
- }
49
+ this.log(`Docs path not found: ${this.options.docsPath}`);
37
50
  return;
38
51
  }
39
52
  const files = this.walkDir(this.options.docsPath);
@@ -55,14 +68,10 @@ export class MCPServer {
55
68
  });
56
69
  }
57
70
  catch (err) {
58
- if (this.options.debug) {
59
- console.log(`[MCP] Failed to index ${file}:`, err);
60
- }
71
+ this.log(`Failed to index ${file}:`, err);
61
72
  }
62
73
  }
63
- if (this.options.debug) {
64
- console.log(`[MCP] Indexed ${this.docsIndex.length} documentation files`);
65
- }
74
+ this.log(`Indexed ${this.docsIndex.length} documentation files`);
66
75
  }
67
76
  walkDir(dir) {
68
77
  const files = [];
@@ -255,9 +264,11 @@ export class MCPServer {
255
264
  }
256
265
  handleRequest(req) {
257
266
  const { method, params, id } = req;
267
+ this.log(`Request: ${method}`, params);
258
268
  try {
259
269
  switch (method) {
260
270
  case 'initialize': {
271
+ this.initialized = true;
261
272
  const response = {
262
273
  protocolVersion: '2024-11-05',
263
274
  capabilities: {
@@ -270,6 +281,9 @@ export class MCPServer {
270
281
  };
271
282
  return { jsonrpc: '2.0', id: id, result: response };
272
283
  }
284
+ case 'notifications/initialized': {
285
+ return { jsonrpc: '2.0', id: id, result: {} };
286
+ }
273
287
  case 'ping':
274
288
  return { jsonrpc: '2.0', id: id, result: {} };
275
289
  case 'tools/list': {
@@ -281,6 +295,10 @@ export class MCPServer {
281
295
  const result = this.handleToolCall(name, args || {});
282
296
  return { jsonrpc: '2.0', id: id, result };
283
297
  }
298
+ case 'resources/list':
299
+ return { jsonrpc: '2.0', id: id, result: { resources: [] } };
300
+ case 'prompts/list':
301
+ return { jsonrpc: '2.0', id: id, result: { prompts: [] } };
284
302
  default:
285
303
  return {
286
304
  jsonrpc: '2.0',
@@ -297,11 +315,46 @@ export class MCPServer {
297
315
  };
298
316
  }
299
317
  }
300
- async start() {
318
+ sendNotification(notification) {
319
+ const data = JSON.stringify(notification);
320
+ for (const client of this.sseClients) {
321
+ client.write(`data: ${data}\n\n`);
322
+ }
323
+ }
324
+ async startStdio() {
325
+ const rl = createInterface({
326
+ input: process.stdin,
327
+ output: process.stdout,
328
+ terminal: false,
329
+ });
330
+ this.log('Starting in stdio mode');
331
+ rl.on('line', (line) => {
332
+ if (!line.trim())
333
+ return;
334
+ try {
335
+ const request = JSON.parse(line);
336
+ const response = this.handleRequest(request);
337
+ process.stdout.write(JSON.stringify(response) + '\n');
338
+ }
339
+ catch (err) {
340
+ const errorResponse = {
341
+ jsonrpc: '2.0',
342
+ id: 0,
343
+ error: { code: -32700, message: 'Parse error' },
344
+ };
345
+ process.stdout.write(JSON.stringify(errorResponse) + '\n');
346
+ }
347
+ });
348
+ rl.on('close', () => {
349
+ this.log('stdin closed, exiting');
350
+ process.exit(0);
351
+ });
352
+ }
353
+ async startHttp() {
301
354
  return new Promise((resolve) => {
302
355
  this.server = createServer((req, res) => {
303
356
  res.setHeader('Access-Control-Allow-Origin', '*');
304
- res.setHeader('Access-Control-Allow-Methods', 'POST, OPTIONS');
357
+ res.setHeader('Access-Control-Allow-Methods', 'POST, GET, OPTIONS');
305
358
  res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
306
359
  if (req.method === 'OPTIONS') {
307
360
  res.writeHead(204);
@@ -318,13 +371,7 @@ export class MCPServer {
318
371
  req.on('end', () => {
319
372
  try {
320
373
  const request = JSON.parse(body);
321
- if (this.options.debug) {
322
- console.log('[MCP] Request:', JSON.stringify(request, null, 2));
323
- }
324
374
  const response = this.handleRequest(request);
325
- if (this.options.debug) {
326
- console.log('[MCP] Response:', JSON.stringify(response, null, 2));
327
- }
328
375
  res.writeHead(200, { 'Content-Type': 'application/json' });
329
376
  res.end(JSON.stringify(response));
330
377
  }
@@ -339,16 +386,99 @@ export class MCPServer {
339
386
  });
340
387
  });
341
388
  this.server.listen(this.options.port, () => {
342
- if (this.options.debug) {
343
- console.log(`[MCP] Server listening on http://localhost:${this.options.port}`);
344
- console.log(`[MCP] Docs path: ${this.options.docsPath}`);
345
- console.log(`[MCP] Indexed ${this.docsIndex.length} files`);
389
+ this.log(`HTTP server listening on http://localhost:${this.options.port}`);
390
+ resolve();
391
+ });
392
+ });
393
+ }
394
+ async startSSE() {
395
+ return new Promise((resolve) => {
396
+ this.server = createServer((req, res) => {
397
+ res.setHeader('Access-Control-Allow-Origin', '*');
398
+ res.setHeader('Access-Control-Allow-Methods', 'POST, GET, OPTIONS');
399
+ res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
400
+ const url = req.url || '/';
401
+ if (req.method === 'OPTIONS') {
402
+ res.writeHead(204);
403
+ res.end();
404
+ return;
346
405
  }
406
+ if (req.method === 'GET' && url === '/sse') {
407
+ res.writeHead(200, {
408
+ 'Content-Type': 'text/event-stream',
409
+ 'Cache-Control': 'no-cache',
410
+ 'Connection': 'keep-alive',
411
+ });
412
+ res.write(`data: ${JSON.stringify({ type: 'connected' })}\n\n`);
413
+ this.sseClients.add(res);
414
+ this.log(`SSE client connected (${this.sseClients.size} total)`);
415
+ req.on('close', () => {
416
+ this.sseClients.delete(res);
417
+ this.log(`SSE client disconnected (${this.sseClients.size} total)`);
418
+ });
419
+ return;
420
+ }
421
+ if (req.method === 'POST') {
422
+ let body = '';
423
+ req.on('data', chunk => body += chunk);
424
+ req.on('end', () => {
425
+ try {
426
+ const request = JSON.parse(body);
427
+ const response = this.handleRequest(request);
428
+ res.writeHead(200, { 'Content-Type': 'application/json' });
429
+ res.end(JSON.stringify(response));
430
+ }
431
+ catch (err) {
432
+ res.writeHead(400, { 'Content-Type': 'application/json' });
433
+ res.end(JSON.stringify({
434
+ jsonrpc: '2.0',
435
+ id: null,
436
+ error: { code: -32700, message: 'Parse error' },
437
+ }));
438
+ }
439
+ });
440
+ return;
441
+ }
442
+ if (req.method === 'GET' && url === '/health') {
443
+ res.writeHead(200, { 'Content-Type': 'application/json' });
444
+ res.end(JSON.stringify({
445
+ status: 'ok',
446
+ name: this.options.name,
447
+ version: this.options.version,
448
+ docsCount: this.docsIndex.length,
449
+ sseClients: this.sseClients.size,
450
+ }));
451
+ return;
452
+ }
453
+ res.writeHead(404);
454
+ res.end('Not found');
455
+ });
456
+ this.server.listen(this.options.port, () => {
457
+ this.log(`SSE server listening on http://localhost:${this.options.port}`);
458
+ this.log(` POST / - JSON-RPC endpoint`);
459
+ this.log(` GET /sse - Server-Sent Events`);
460
+ this.log(` GET /health - Health check`);
347
461
  resolve();
348
462
  });
349
463
  });
350
464
  }
465
+ async start() {
466
+ switch (this.options.transport) {
467
+ case 'stdio':
468
+ return this.startStdio();
469
+ case 'http':
470
+ return this.startHttp();
471
+ case 'sse':
472
+ return this.startSSE();
473
+ default:
474
+ throw new Error(`Unknown transport: ${this.options.transport}`);
475
+ }
476
+ }
351
477
  async stop() {
478
+ for (const client of this.sseClients) {
479
+ client.end();
480
+ }
481
+ this.sseClients.clear();
352
482
  return new Promise((resolve) => {
353
483
  if (this.server) {
354
484
  this.server.close(() => resolve());
@@ -364,6 +494,9 @@ export class MCPServer {
364
494
  getDocsCount() {
365
495
  return this.docsIndex.length;
366
496
  }
497
+ getTransport() {
498
+ return this.options.transport;
499
+ }
367
500
  }
368
501
  export function createMCPServer(options) {
369
502
  return new MCPServer(options);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "recker",
3
- "version": "1.0.11-alpha.d342d95",
3
+ "version": "1.0.11-alpha.e84cc95",
4
4
  "description": "AI & DevX focused HTTP client for Node.js 18+",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",