opencodekit 0.9.1 → 0.10.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.
@@ -0,0 +1,154 @@
1
+ ---
2
+ name: v0
3
+ description: AI-powered UI generation via v0 MCP. Create chats, generate React components, get design assistance. Use when building UI components, dashboards, or need AI design help.
4
+ mcp:
5
+ v0:
6
+ command: npx
7
+ args:
8
+ [
9
+ "mcp-remote",
10
+ "https://mcp.v0.dev",
11
+ "--header",
12
+ "Authorization: Bearer ${V0_API_KEY}",
13
+ ]
14
+ env:
15
+ V0_API_KEY: "${V0_API_KEY}"
16
+ ---
17
+
18
+ # v0 AI Code Generation (MCP)
19
+
20
+ Access v0's AI-powered code generation via MCP. When this skill is loaded, the `v0` MCP server auto-starts and exposes tools for creating and managing v0 chats.
21
+
22
+ ## Prerequisites
23
+
24
+ Set your v0 API key as an environment variable:
25
+
26
+ ```bash
27
+ export V0_API_KEY="your-v0-api-key"
28
+ ```
29
+
30
+ To get your v0 API key:
31
+
32
+ 1. Go to [v0 account settings](https://v0.app/chat/settings/keys)
33
+ 2. Create a new API key
34
+ 3. Copy and set as environment variable
35
+
36
+ ## Quick Start
37
+
38
+ After loading this skill, list available tools:
39
+
40
+ ```
41
+ skill_mcp(skill_name="v0", list_tools=true)
42
+ ```
43
+
44
+ Then invoke tools:
45
+
46
+ ```
47
+ skill_mcp(skill_name="v0", tool_name="create_chat", arguments='{"prompt": "Create a React dashboard component with a sidebar"}')
48
+ ```
49
+
50
+ ## Available Tools
51
+
52
+ ### create_chat
53
+
54
+ Create a new v0 chat with a prompt.
55
+
56
+ | Parameter | Type | Required | Description |
57
+ | --------- | ------ | -------- | ---------------------------------- |
58
+ | `prompt` | string | Yes | The prompt for v0 to generate code |
59
+
60
+ **Example:**
61
+
62
+ ```
63
+ skill_mcp(skill_name="v0", tool_name="create_chat", arguments='{"prompt": "Build a responsive navbar with dark mode toggle"}')
64
+ ```
65
+
66
+ ### get_chat
67
+
68
+ Get details about an existing chat.
69
+
70
+ | Parameter | Type | Required | Description |
71
+ | --------- | ------ | -------- | -------------- |
72
+ | `chatId` | string | Yes | The v0 chat ID |
73
+
74
+ **Example:**
75
+
76
+ ```
77
+ skill_mcp(skill_name="v0", tool_name="get_chat", arguments='{"chatId": "abc123"}')
78
+ ```
79
+
80
+ ### find_chats
81
+
82
+ Search through your v0 chats.
83
+
84
+ | Parameter | Type | Required | Description |
85
+ | --------- | ------ | -------- | ------------ |
86
+ | `query` | string | No | Search query |
87
+
88
+ **Example:**
89
+
90
+ ```
91
+ skill_mcp(skill_name="v0", tool_name="find_chats", arguments='{"query": "React components"}')
92
+ ```
93
+
94
+ ### send_message
95
+
96
+ Continue a conversation in an existing chat.
97
+
98
+ | Parameter | Type | Required | Description |
99
+ | --------- | ------ | -------- | --------------- |
100
+ | `chatId` | string | Yes | The v0 chat ID |
101
+ | `message` | string | Yes | Message to send |
102
+
103
+ **Example:**
104
+
105
+ ```
106
+ skill_mcp(skill_name="v0", tool_name="send_message", arguments='{"chatId": "abc123", "message": "Add dark mode support"}')
107
+ ```
108
+
109
+ ## Workflow
110
+
111
+ ### 1. Generate a New Component
112
+
113
+ ```
114
+ # Create a new chat with your requirements
115
+ skill_mcp(skill_name="v0", tool_name="create_chat", arguments='{"prompt": "Create a modern pricing table with 3 tiers using Tailwind CSS"}')
116
+
117
+ # The response includes a chat ID and generated code
118
+ ```
119
+
120
+ ### 2. Iterate on Design
121
+
122
+ ```
123
+ # Send follow-up messages to refine
124
+ skill_mcp(skill_name="v0", tool_name="send_message", arguments='{"chatId": "chat-id-here", "message": "Make the recommended tier more prominent with a gradient border"}')
125
+ ```
126
+
127
+ ### 3. Search Previous Work
128
+
129
+ ```
130
+ # Find relevant previous chats
131
+ skill_mcp(skill_name="v0", tool_name="find_chats", arguments='{"query": "dashboard"}')
132
+ ```
133
+
134
+ ## Use Cases
135
+
136
+ - **Component Generation**: Create React/Next.js components from descriptions
137
+ - **UI Prototyping**: Rapidly prototype UI ideas
138
+ - **Design Iteration**: Refine designs through conversation
139
+ - **Code Assistance**: Get help with Tailwind CSS, responsive design, etc.
140
+
141
+ ## Tips
142
+
143
+ - **Be specific** in prompts - include framework (React, Next.js), styling (Tailwind, CSS), and functionality details
144
+ - **Iterate** using `send_message` rather than creating new chats for refinements
145
+ - **Search first** with `find_chats` to reuse previous work
146
+ - **Include context** about your existing design system or component patterns
147
+
148
+ ## Troubleshooting
149
+
150
+ **"Invalid API key"**: Ensure `V0_API_KEY` environment variable is set correctly.
151
+
152
+ **"Rate limit exceeded"**: v0 has usage limits. Wait a few minutes or check your plan.
153
+
154
+ **"Chat not found"**: Verify the chat ID is correct. Use `find_chats` to list available chats.
@@ -14,7 +14,13 @@ import { lspManager } from "../lib/lsp/client";
14
14
  import { resolveServer } from "../lib/lsp/config";
15
15
  import type {
16
16
  CodeAction,
17
+ Diagnostic,
18
+ DocumentSymbol,
19
+ HoverResult,
20
+ Location,
21
+ LocationLink,
17
22
  PrepareRenameResult,
23
+ SymbolInfo,
18
24
  WorkspaceEdit,
19
25
  } from "../lib/lsp/types";
20
26
  import {
@@ -328,5 +334,453 @@ Note: File was modified directly. Re-read before editing.`;
328
334
  },
329
335
  });
330
336
 
337
+ /**
338
+ * Get hover information (type/documentation) at a position
339
+ */
340
+ export const lsp_hover = tool({
341
+ description: `Get type information and documentation for a symbol at a specific position.
342
+
343
+ Returns type signatures, JSDoc/docstrings, and other hover information the language server provides.
344
+ Useful for understanding what a variable/function is without reading its definition.`,
345
+
346
+ args: {
347
+ filePath: tool.schema.string().describe("Path to the file"),
348
+ line: tool.schema.number().describe("Line number (1-based)"),
349
+ character: tool.schema.number().describe("Character position (0-based)"),
350
+ },
351
+
352
+ async execute(args) {
353
+ const cwd = process.cwd();
354
+ const absPath = resolve(cwd, args.filePath);
355
+ const server = resolveServer(absPath);
356
+
357
+ if (!server) {
358
+ return `Error: No LSP server available for file type: ${absPath}`;
359
+ }
360
+
361
+ try {
362
+ const client = await lspManager.getClient(cwd, server);
363
+ const result = (await client.hover(
364
+ absPath,
365
+ args.line,
366
+ args.character,
367
+ )) as HoverResult | null;
368
+ lspManager.releaseClient(cwd, server.id);
369
+
370
+ if (!result) {
371
+ return "No hover information available at this position.";
372
+ }
373
+
374
+ return formatHoverResult(result);
375
+ } catch (error) {
376
+ return `Error: ${error instanceof Error ? error.message : String(error)}`;
377
+ }
378
+ },
379
+ });
380
+
381
+ /**
382
+ * Go to definition of a symbol
383
+ */
384
+ export const lsp_goto_definition = tool({
385
+ description: `Find the definition location of a symbol at a specific position.
386
+
387
+ Returns the file path and position where the symbol is defined.
388
+ Works for functions, variables, classes, types, imports, etc.`,
389
+
390
+ args: {
391
+ filePath: tool.schema.string().describe("Path to the file"),
392
+ line: tool.schema.number().describe("Line number (1-based)"),
393
+ character: tool.schema.number().describe("Character position (0-based)"),
394
+ },
395
+
396
+ async execute(args) {
397
+ const cwd = process.cwd();
398
+ const absPath = resolve(cwd, args.filePath);
399
+ const server = resolveServer(absPath);
400
+
401
+ if (!server) {
402
+ return `Error: No LSP server available for file type: ${absPath}`;
403
+ }
404
+
405
+ try {
406
+ const client = await lspManager.getClient(cwd, server);
407
+ const result = (await client.definition(
408
+ absPath,
409
+ args.line,
410
+ args.character,
411
+ )) as Location | Location[] | LocationLink[] | null;
412
+ lspManager.releaseClient(cwd, server.id);
413
+
414
+ if (!result) {
415
+ return "No definition found at this position.";
416
+ }
417
+
418
+ return formatLocations(result);
419
+ } catch (error) {
420
+ return `Error: ${error instanceof Error ? error.message : String(error)}`;
421
+ }
422
+ },
423
+ });
424
+
425
+ /**
426
+ * Find all references to a symbol
427
+ */
428
+ export const lsp_find_references = tool({
429
+ description: `Find all references to a symbol across the workspace.
430
+
431
+ Returns all locations where the symbol is used, including the definition.
432
+ Useful for understanding usage before refactoring.`,
433
+
434
+ args: {
435
+ filePath: tool.schema.string().describe("Path to the file"),
436
+ line: tool.schema.number().describe("Line number (1-based)"),
437
+ character: tool.schema.number().describe("Character position (0-based)"),
438
+ includeDeclaration: tool.schema
439
+ .boolean()
440
+ .optional()
441
+ .describe("Include the declaration itself (default: true)"),
442
+ },
443
+
444
+ async execute(args) {
445
+ const cwd = process.cwd();
446
+ const absPath = resolve(cwd, args.filePath);
447
+ const server = resolveServer(absPath);
448
+
449
+ if (!server) {
450
+ return `Error: No LSP server available for file type: ${absPath}`;
451
+ }
452
+
453
+ try {
454
+ const client = await lspManager.getClient(cwd, server);
455
+ const result = (await client.references(
456
+ absPath,
457
+ args.line,
458
+ args.character,
459
+ args.includeDeclaration ?? true,
460
+ )) as Location[] | null;
461
+ lspManager.releaseClient(cwd, server.id);
462
+
463
+ if (!result || result.length === 0) {
464
+ return "No references found.";
465
+ }
466
+
467
+ const locations = result.slice(0, 50); // Limit results
468
+ const formatted = locations.map((loc) => formatLocation(loc)).join("\n");
469
+ const suffix =
470
+ result.length > 50 ? `\n\n... and ${result.length - 50} more` : "";
471
+
472
+ return `Found ${result.length} reference(s):\n\n${formatted}${suffix}`;
473
+ } catch (error) {
474
+ return `Error: ${error instanceof Error ? error.message : String(error)}`;
475
+ }
476
+ },
477
+ });
478
+
479
+ /**
480
+ * Get document symbols (outline) for a file
481
+ */
482
+ export const lsp_document_symbols = tool({
483
+ description: `Get the symbol outline of a file (classes, functions, variables, etc.).
484
+
485
+ Returns a hierarchical structure of all symbols in the file.
486
+ Useful for understanding file structure quickly.`,
487
+
488
+ args: {
489
+ filePath: tool.schema.string().describe("Path to the file"),
490
+ },
491
+
492
+ async execute(args) {
493
+ const cwd = process.cwd();
494
+ const absPath = resolve(cwd, args.filePath);
495
+ const server = resolveServer(absPath);
496
+
497
+ if (!server) {
498
+ return `Error: No LSP server available for file type: ${absPath}`;
499
+ }
500
+
501
+ try {
502
+ const client = await lspManager.getClient(cwd, server);
503
+ const result = (await client.documentSymbols(absPath)) as
504
+ | DocumentSymbol[]
505
+ | SymbolInfo[]
506
+ | null;
507
+ lspManager.releaseClient(cwd, server.id);
508
+
509
+ if (!result || result.length === 0) {
510
+ return "No symbols found in this file.";
511
+ }
512
+
513
+ return formatDocumentSymbols(result);
514
+ } catch (error) {
515
+ return `Error: ${error instanceof Error ? error.message : String(error)}`;
516
+ }
517
+ },
518
+ });
519
+
520
+ /**
521
+ * Search for symbols across the workspace
522
+ */
523
+ export const lsp_workspace_symbols = tool({
524
+ description: `Search for symbols by name across the entire workspace.
525
+
526
+ Performs fuzzy matching on symbol names (functions, classes, variables, etc.).
527
+ Useful for finding where something is defined without knowing the exact file.`,
528
+
529
+ args: {
530
+ query: tool.schema
531
+ .string()
532
+ .describe("Symbol name to search for (fuzzy match)"),
533
+ filePath: tool.schema
534
+ .string()
535
+ .optional()
536
+ .describe("Any file in the project (used to determine language server)"),
537
+ },
538
+
539
+ async execute(args) {
540
+ const cwd = process.cwd();
541
+ const absPath = args.filePath
542
+ ? resolve(cwd, args.filePath)
543
+ : resolve(cwd, ".");
544
+ const server = resolveServer(absPath);
545
+
546
+ if (!server) {
547
+ return `Error: No LSP server available. Provide a filePath to help identify the language.`;
548
+ }
549
+
550
+ try {
551
+ const client = await lspManager.getClient(cwd, server);
552
+ const result = (await client.workspaceSymbols(args.query)) as
553
+ | SymbolInfo[]
554
+ | null;
555
+ lspManager.releaseClient(cwd, server.id);
556
+
557
+ if (!result || result.length === 0) {
558
+ return `No symbols found matching "${args.query}".`;
559
+ }
560
+
561
+ const symbols = result.slice(0, 30); // Limit results
562
+ const formatted = symbols
563
+ .map(
564
+ (sym) =>
565
+ `${getSymbolKindName(sym.kind)} ${sym.name}${sym.containerName ? ` (in ${sym.containerName})` : ""}\n ${formatUri(sym.location.uri)}:${sym.location.range.start.line + 1}`,
566
+ )
567
+ .join("\n\n");
568
+ const suffix =
569
+ result.length > 30 ? `\n\n... and ${result.length - 30} more` : "";
570
+
571
+ return `Found ${result.length} symbol(s):\n\n${formatted}${suffix}`;
572
+ } catch (error) {
573
+ return `Error: ${error instanceof Error ? error.message : String(error)}`;
574
+ }
575
+ },
576
+ });
577
+
578
+ /**
579
+ * Get diagnostics (errors, warnings) for a file
580
+ */
581
+ export const lsp_diagnostics = tool({
582
+ description: `Get diagnostics (errors, warnings, hints) for a file from the language server.
583
+
584
+ Returns type errors, lint warnings, and other issues detected by the language server.
585
+ Useful for checking if code has problems before running tests.`,
586
+
587
+ args: {
588
+ filePath: tool.schema.string().describe("Path to the file"),
589
+ severity: tool.schema
590
+ .string()
591
+ .optional()
592
+ .describe(
593
+ "Filter by severity: 'error', 'warning', 'info', 'hint', or 'all' (default: all)",
594
+ ),
595
+ },
596
+
597
+ async execute(args) {
598
+ const cwd = process.cwd();
599
+ const absPath = resolve(cwd, args.filePath);
600
+ const server = resolveServer(absPath);
601
+
602
+ if (!server) {
603
+ return `Error: No LSP server available for file type: ${absPath}`;
604
+ }
605
+
606
+ try {
607
+ const client = await lspManager.getClient(cwd, server);
608
+ const result = await client.diagnostics(absPath);
609
+ lspManager.releaseClient(cwd, server.id);
610
+
611
+ let diagnostics = result.items || [];
612
+
613
+ // Filter by severity if specified
614
+ if (args.severity && args.severity !== "all") {
615
+ const severityMap: Record<string, number> = {
616
+ error: 1,
617
+ warning: 2,
618
+ info: 3,
619
+ hint: 4,
620
+ };
621
+ const targetSeverity = severityMap[args.severity.toLowerCase()];
622
+ if (targetSeverity) {
623
+ diagnostics = diagnostics.filter(
624
+ (d) => d.severity === targetSeverity,
625
+ );
626
+ }
627
+ }
628
+
629
+ if (diagnostics.length === 0) {
630
+ return args.severity && args.severity !== "all"
631
+ ? `No ${args.severity} diagnostics found.`
632
+ : "No diagnostics found. File appears clean.";
633
+ }
634
+
635
+ return formatDiagnostics(diagnostics, args.filePath);
636
+ } catch (error) {
637
+ return `Error: ${error instanceof Error ? error.message : String(error)}`;
638
+ }
639
+ },
640
+ });
641
+
642
+ // ============ Helper Functions ============
643
+
644
+ function formatHoverResult(hover: HoverResult): string {
645
+ const contents = hover.contents;
646
+
647
+ if (typeof contents === "string") {
648
+ return contents;
649
+ }
650
+
651
+ if (Array.isArray(contents)) {
652
+ return contents
653
+ .map((c) => (typeof c === "string" ? c : c.value))
654
+ .join("\n\n");
655
+ }
656
+
657
+ if (typeof contents === "object" && "value" in contents) {
658
+ return contents.value;
659
+ }
660
+
661
+ return JSON.stringify(contents);
662
+ }
663
+
664
+ function formatUri(uri: string): string {
665
+ return uri.replace("file://", "").replace(process.cwd() + "/", "");
666
+ }
667
+
668
+ function formatLocation(loc: Location | LocationLink): string {
669
+ if ("targetUri" in loc) {
670
+ return `${formatUri(loc.targetUri)}:${loc.targetRange.start.line + 1}:${loc.targetRange.start.character}`;
671
+ }
672
+ return `${formatUri(loc.uri)}:${loc.range.start.line + 1}:${loc.range.start.character}`;
673
+ }
674
+
675
+ function formatLocations(
676
+ result: Location | Location[] | LocationLink[],
677
+ ): string {
678
+ const locations = Array.isArray(result) ? result : [result];
679
+
680
+ if (locations.length === 0) {
681
+ return "No locations found.";
682
+ }
683
+
684
+ if (locations.length === 1) {
685
+ return `Definition: ${formatLocation(locations[0] as Location | LocationLink)}`;
686
+ }
687
+
688
+ return `Found ${locations.length} location(s):\n${locations.map((l) => ` ${formatLocation(l as Location | LocationLink)}`).join("\n")}`;
689
+ }
690
+
691
+ function formatDocumentSymbols(
692
+ symbols: DocumentSymbol[] | SymbolInfo[],
693
+ indent = 0,
694
+ ): string {
695
+ const prefix = " ".repeat(indent);
696
+ const lines: string[] = [];
697
+
698
+ for (const sym of symbols) {
699
+ const kindName = getSymbolKindName(sym.kind);
700
+ const line =
701
+ "range" in sym
702
+ ? sym.range.start.line + 1
703
+ : "location" in sym
704
+ ? sym.location.range.start.line + 1
705
+ : "?";
706
+
707
+ lines.push(`${prefix}${kindName} ${sym.name} (line ${line})`);
708
+
709
+ if ("children" in sym && sym.children) {
710
+ lines.push(formatDocumentSymbols(sym.children, indent + 1));
711
+ }
712
+ }
713
+
714
+ return lines.join("\n");
715
+ }
716
+
717
+ function formatDiagnostics(
718
+ diagnostics: Diagnostic[],
719
+ filePath: string,
720
+ ): string {
721
+ const severityNames = ["", "Error", "Warning", "Info", "Hint"];
722
+
723
+ const sorted = [...diagnostics].sort((a, b) => {
724
+ const sevA = a.severity ?? 4;
725
+ const sevB = b.severity ?? 4;
726
+ if (sevA !== sevB) return sevA - sevB;
727
+ return a.range.start.line - b.range.start.line;
728
+ });
729
+
730
+ const lines = sorted.map((d) => {
731
+ const sev = severityNames[d.severity ?? 4] || "Unknown";
732
+ const line = d.range.start.line + 1;
733
+ const col = d.range.start.character + 1;
734
+ const source = d.source ? `[${d.source}]` : "";
735
+ const code = d.code ? ` (${d.code})` : "";
736
+ return `${sev}: ${filePath}:${line}:${col}${code} ${source}\n ${d.message}`;
737
+ });
738
+
739
+ const errorCount = diagnostics.filter((d) => d.severity === 1).length;
740
+ const warnCount = diagnostics.filter((d) => d.severity === 2).length;
741
+
742
+ let summary = `Found ${diagnostics.length} diagnostic(s)`;
743
+ if (errorCount > 0 || warnCount > 0) {
744
+ const parts: string[] = [];
745
+ if (errorCount > 0) parts.push(`${errorCount} error(s)`);
746
+ if (warnCount > 0) parts.push(`${warnCount} warning(s)`);
747
+ summary += `: ${parts.join(", ")}`;
748
+ }
749
+
750
+ return `${summary}\n\n${lines.join("\n\n")}`;
751
+ }
752
+
753
+ function getSymbolKindName(kind: number): string {
754
+ const kinds: Record<number, string> = {
755
+ 1: "File",
756
+ 2: "Module",
757
+ 3: "Namespace",
758
+ 4: "Package",
759
+ 5: "Class",
760
+ 6: "Method",
761
+ 7: "Property",
762
+ 8: "Field",
763
+ 9: "Constructor",
764
+ 10: "Enum",
765
+ 11: "Interface",
766
+ 12: "Function",
767
+ 13: "Variable",
768
+ 14: "Constant",
769
+ 15: "String",
770
+ 16: "Number",
771
+ 17: "Boolean",
772
+ 18: "Array",
773
+ 19: "Object",
774
+ 20: "Key",
775
+ 21: "Null",
776
+ 22: "EnumMember",
777
+ 23: "Struct",
778
+ 24: "Event",
779
+ 25: "Operator",
780
+ 26: "TypeParameter",
781
+ };
782
+ return kinds[kind] || `Kind(${kind})`;
783
+ }
784
+
331
785
  // Default export for single-tool registration (uses rename as primary)
332
786
  export default lsp_rename;