skilllite 0.1.0__py3-none-any.whl → 0.1.1__py3-none-any.whl

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.
skilllite/mcp/server.py CHANGED
@@ -4,15 +4,22 @@ MCP Server implementation for SkillLite.
4
4
  This module provides a secure code execution sandbox server implementing
5
5
  the Model Context Protocol (MCP).
6
6
 
7
+ Features:
8
+ 1. **Skills Management**: List, inspect, and execute pre-defined skills
9
+ from the skills directory.
10
+
11
+ 2. **Code Execution**: Execute arbitrary code in a secure sandbox with
12
+ security scanning.
13
+
7
14
  Security Model:
8
15
  The MCP server implements a two-phase execution model for security:
9
-
16
+
10
17
  1. **scan_code**: First, scan the code for security issues. This returns
11
18
  a detailed report of any potential risks found in the code.
12
-
19
+
13
20
  2. **execute_code**: Then, execute the code. If security issues were found,
14
21
  the caller must explicitly set `confirmed=true` to proceed.
15
-
22
+
16
23
  This allows LLM clients to present security warnings to users and get
17
24
  explicit confirmation before executing potentially dangerous code.
18
25
 
@@ -20,6 +27,7 @@ Environment Variables:
20
27
  SKILLBOX_SANDBOX_LEVEL: Default sandbox level (1/2/3, default: 3)
21
28
  SKILLBOX_PATH: Path to skillbox binary
22
29
  MCP_SANDBOX_TIMEOUT: Execution timeout in seconds (default: 30)
30
+ SKILLLITE_SKILLS_DIR: Directory containing skills (default: ./.skills)
23
31
  """
24
32
 
25
33
  import asyncio
@@ -494,21 +502,31 @@ This skill executes code from MCP.
494
502
  class MCPServer:
495
503
  """MCP server for SkillLite sandbox execution.
496
504
 
497
- This server provides two tools for secure code execution:
498
-
499
- 1. **scan_code**: Scan code for security issues before execution.
505
+ This server provides tools for skills management and secure code execution:
506
+
507
+ Skills Tools:
508
+ 1. **list_skills**: List all available skills in the skills directory.
509
+ 2. **get_skill_info**: Get detailed information about a specific skill.
510
+ 3. **run_skill**: Execute a skill with given input parameters.
511
+
512
+ Code Execution Tools:
513
+ 4. **scan_code**: Scan code for security issues before execution.
500
514
  Returns a detailed report and a scan_id for confirmation.
501
-
502
- 2. **execute_code**: Execute code in a sandbox. If high-severity
515
+ 5. **execute_code**: Execute code in a sandbox. If high-severity
503
516
  security issues are found, requires explicit confirmation.
504
-
505
- Example workflow:
517
+
518
+ Example workflow for skills:
519
+ 1. Call list_skills to see available skills
520
+ 2. Call get_skill_info to understand a skill's parameters
521
+ 3. Call run_skill with the required input
522
+
523
+ Example workflow for code execution:
506
524
  1. Call scan_code to check for security issues
507
525
  2. Review the security report with the user
508
526
  3. If user approves, call execute_code with confirmed=true and scan_id
509
527
  """
510
-
511
- def __init__(self):
528
+
529
+ def __init__(self, skills_dir: Optional[str] = None):
512
530
  if not MCP_AVAILABLE:
513
531
  raise ImportError(
514
532
  "MCP library not available. Please install it with: "
@@ -516,14 +534,84 @@ class MCPServer:
516
534
  )
517
535
  self.server = Server("skilllite-mcp-server")
518
536
  self.executor = SandboxExecutor()
537
+
538
+ # Initialize SkillManager for skills support
539
+ self.skills_dir = skills_dir or os.environ.get("SKILLLITE_SKILLS_DIR", "./.skills")
540
+ self.skill_manager = None
541
+ self._init_skill_manager()
542
+
519
543
  self._setup_handlers()
544
+
545
+ def _init_skill_manager(self):
546
+ """Initialize the SkillManager if skills directory exists."""
547
+ try:
548
+ from ..core.manager import SkillManager
549
+ skills_path = Path(self.skills_dir)
550
+ if skills_path.exists():
551
+ self.skill_manager = SkillManager(skills_dir=str(skills_path))
552
+ except Exception as e:
553
+ # Skills not available, continue without them
554
+ pass
520
555
 
521
556
  def _setup_handlers(self):
522
557
  """Setup MCP server handlers."""
523
-
558
+
524
559
  @self.server.list_tools()
525
560
  async def list_tools() -> List[Tool]:
526
- return [
561
+ tools = [
562
+ # Skills management tools
563
+ Tool(
564
+ name="list_skills",
565
+ description=(
566
+ "List all available skills in the skills directory. "
567
+ "Returns skill names, descriptions, and languages."
568
+ ),
569
+ inputSchema={
570
+ "type": "object",
571
+ "properties": {},
572
+ "required": []
573
+ }
574
+ ),
575
+ Tool(
576
+ name="get_skill_info",
577
+ description=(
578
+ "Get detailed information about a specific skill, "
579
+ "including its input schema, description, and usage."
580
+ ),
581
+ inputSchema={
582
+ "type": "object",
583
+ "properties": {
584
+ "skill_name": {
585
+ "type": "string",
586
+ "description": "Name of the skill to get info for"
587
+ }
588
+ },
589
+ "required": ["skill_name"]
590
+ }
591
+ ),
592
+ Tool(
593
+ name="run_skill",
594
+ description=(
595
+ "Execute a skill with the given input parameters. "
596
+ "Use list_skills to see available skills and "
597
+ "get_skill_info to understand required parameters."
598
+ ),
599
+ inputSchema={
600
+ "type": "object",
601
+ "properties": {
602
+ "skill_name": {
603
+ "type": "string",
604
+ "description": "Name of the skill to execute"
605
+ },
606
+ "input": {
607
+ "type": "object",
608
+ "description": "Input parameters for the skill"
609
+ }
610
+ },
611
+ "required": ["skill_name"]
612
+ }
613
+ ),
614
+ # Code execution tools
527
615
  Tool(
528
616
  name="scan_code",
529
617
  description=(
@@ -595,13 +683,22 @@ class MCPServer:
595
683
  }
596
684
  )
597
685
  ]
686
+ return tools
598
687
 
599
688
  @self.server.call_tool()
600
689
  async def call_tool(
601
690
  name: str,
602
691
  arguments: Dict[str, Any]
603
692
  ) -> "CallToolResult":
604
- if name == "scan_code":
693
+ # Skills management tools
694
+ if name == "list_skills":
695
+ return await self._handle_list_skills(arguments)
696
+ elif name == "get_skill_info":
697
+ return await self._handle_get_skill_info(arguments)
698
+ elif name == "run_skill":
699
+ return await self._handle_run_skill(arguments)
700
+ # Code execution tools
701
+ elif name == "scan_code":
605
702
  return await self._handle_scan_code(arguments)
606
703
  elif name == "execute_code":
607
704
  return await self._handle_execute_code(arguments)
@@ -712,7 +809,190 @@ class MCPServer:
712
809
  )
713
810
  ]
714
811
  )
715
-
812
+
813
+ async def _handle_list_skills(self, arguments: Dict[str, Any]) -> "CallToolResult":
814
+ """Handle list_skills tool call."""
815
+ if not self.skill_manager:
816
+ return CallToolResult(
817
+ content=[
818
+ TextContent(
819
+ type="text",
820
+ text=f"No skills available. Skills directory not found: {self.skills_dir}"
821
+ )
822
+ ]
823
+ )
824
+
825
+ skills = self.skill_manager.list_skills()
826
+ if not skills:
827
+ return CallToolResult(
828
+ content=[
829
+ TextContent(
830
+ type="text",
831
+ text=f"No skills found in directory: {self.skills_dir}"
832
+ )
833
+ ]
834
+ )
835
+
836
+ # Format skills list
837
+ lines = ["Available Skills:", ""]
838
+ for skill in skills:
839
+ lines.append(f"• **{skill.name}**")
840
+ if skill.description:
841
+ lines.append(f" {skill.description}")
842
+ if skill.language:
843
+ lines.append(f" Language: {skill.language}")
844
+ lines.append("")
845
+
846
+ return CallToolResult(
847
+ content=[
848
+ TextContent(
849
+ type="text",
850
+ text="\n".join(lines)
851
+ )
852
+ ]
853
+ )
854
+
855
+ async def _handle_get_skill_info(self, arguments: Dict[str, Any]) -> "CallToolResult":
856
+ """Handle get_skill_info tool call."""
857
+ skill_name = arguments.get("skill_name")
858
+
859
+ if not skill_name:
860
+ return CallToolResult(
861
+ isError=True,
862
+ content=[
863
+ TextContent(
864
+ type="text",
865
+ text="Missing required argument: skill_name"
866
+ )
867
+ ]
868
+ )
869
+
870
+ if not self.skill_manager:
871
+ return CallToolResult(
872
+ isError=True,
873
+ content=[
874
+ TextContent(
875
+ type="text",
876
+ text=f"No skills available. Skills directory not found: {self.skills_dir}"
877
+ )
878
+ ]
879
+ )
880
+
881
+ skill = self.skill_manager.get_skill(skill_name)
882
+ if not skill:
883
+ available = ", ".join(self.skill_manager.skill_names()) or "none"
884
+ return CallToolResult(
885
+ isError=True,
886
+ content=[
887
+ TextContent(
888
+ type="text",
889
+ text=f"Skill not found: {skill_name}\nAvailable skills: {available}"
890
+ )
891
+ ]
892
+ )
893
+
894
+ # Format skill info
895
+ lines = [f"# {skill.name}", ""]
896
+ if skill.description:
897
+ lines.append(skill.description)
898
+ lines.append("")
899
+
900
+ lines.append("## Details")
901
+ lines.append(f"- **Language**: {skill.language or 'unknown'}")
902
+ lines.append(f"- **Path**: {skill.path}")
903
+ if skill.entry_point:
904
+ lines.append(f"- **Entry Point**: {skill.entry_point}")
905
+
906
+ # Add input schema if available
907
+ if skill.input_schema:
908
+ lines.append("")
909
+ lines.append("## Input Schema")
910
+ lines.append("```json")
911
+ lines.append(json.dumps(skill.input_schema, indent=2))
912
+ lines.append("```")
913
+
914
+ return CallToolResult(
915
+ content=[
916
+ TextContent(
917
+ type="text",
918
+ text="\n".join(lines)
919
+ )
920
+ ]
921
+ )
922
+
923
+ async def _handle_run_skill(self, arguments: Dict[str, Any]) -> "CallToolResult":
924
+ """Handle run_skill tool call."""
925
+ skill_name = arguments.get("skill_name")
926
+ input_data = arguments.get("input", {})
927
+
928
+ if not skill_name:
929
+ return CallToolResult(
930
+ isError=True,
931
+ content=[
932
+ TextContent(
933
+ type="text",
934
+ text="Missing required argument: skill_name"
935
+ )
936
+ ]
937
+ )
938
+
939
+ if not self.skill_manager:
940
+ return CallToolResult(
941
+ isError=True,
942
+ content=[
943
+ TextContent(
944
+ type="text",
945
+ text=f"No skills available. Skills directory not found: {self.skills_dir}"
946
+ )
947
+ ]
948
+ )
949
+
950
+ if not self.skill_manager.has_skill(skill_name):
951
+ available = ", ".join(self.skill_manager.skill_names()) or "none"
952
+ return CallToolResult(
953
+ isError=True,
954
+ content=[
955
+ TextContent(
956
+ type="text",
957
+ text=f"Skill not found: {skill_name}\nAvailable skills: {available}"
958
+ )
959
+ ]
960
+ )
961
+
962
+ # Execute the skill
963
+ try:
964
+ result = self.skill_manager.execute(skill_name, input_data)
965
+
966
+ if result.success:
967
+ return CallToolResult(
968
+ content=[
969
+ TextContent(
970
+ type="text",
971
+ text=f"Skill '{skill_name}' executed successfully.\n\nOutput:\n{result.output}"
972
+ )
973
+ ]
974
+ )
975
+ else:
976
+ return CallToolResult(
977
+ isError=True,
978
+ content=[
979
+ TextContent(
980
+ type="text",
981
+ text=f"Skill '{skill_name}' execution failed.\n\nError:\n{result.error}"
982
+ )
983
+ ]
984
+ )
985
+ except Exception as e:
986
+ return CallToolResult(
987
+ isError=True,
988
+ content=[
989
+ TextContent(
990
+ type="text",
991
+ text=f"Error executing skill '{skill_name}': {str(e)}"
992
+ )
993
+ ]
994
+ )
995
+
716
996
  async def run(self):
717
997
  """Run the MCP server."""
718
998
  async with stdio_server() as (read_stream, write_stream):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: skilllite
3
- Version: 0.1.0
3
+ Version: 0.1.1
4
4
  Summary: A lightweight Skills execution engine with LLM integration for LLM agents
5
5
  Author-email: SkillLite Team <skilllite@example.com>
6
6
  License: MIT
@@ -288,6 +288,59 @@ Enum for LLM provider formats:
288
288
  - `ToolFormat.CLAUDE`
289
289
  - `ToolFormat.OPENAI`
290
290
 
291
+ ## OpenCode Integration
292
+
293
+ SkillLite can be integrated with [OpenCode](https://github.com/opencode-ai/opencode) as an MCP (Model Context Protocol) server, providing secure sandbox execution capabilities.
294
+
295
+ ### Quick Setup
296
+
297
+ ```bash
298
+ # Install with MCP support
299
+ pip install skilllite[mcp]
300
+
301
+ # One-command setup for OpenCode
302
+ skilllite init-opencode
303
+
304
+ # Start OpenCode
305
+ opencode
306
+ ```
307
+
308
+ The `init-opencode` command automatically:
309
+ - Detects the best way to start the MCP server (uvx, pipx, skilllite, or python)
310
+ - Creates `opencode.json` with optimal configuration
311
+ - Generates `.opencode/skills/skilllite/SKILL.md` with usage instructions
312
+ - Discovers your pre-defined skills
313
+
314
+ ### Available MCP Tools
315
+
316
+ | Tool | Description |
317
+ |------|-------------|
318
+ | `skilllite_list_skills` | List all available skills |
319
+ | `skilllite_get_skill_info` | Get skill details and input schema |
320
+ | `skilllite_run_skill` | Execute a pre-defined skill |
321
+ | `skilllite_scan_code` | Scan code for security issues |
322
+ | `skilllite_execute_code` | Execute code in secure sandbox |
323
+
324
+ ### Security Features
325
+
326
+ - **System-level Sandbox**: macOS Seatbelt / Linux Namespace isolation
327
+ - **Security Scanning**: Static analysis before execution
328
+ - **User Confirmation**: Dangerous code requires explicit approval
329
+ - **Scan ID Verification**: Prevents code modification between scan and execution
330
+
331
+ For detailed documentation, see [OpenCode Integration Tutorial](../tutorials/07_opencode_integration/README.md).
332
+
333
+ ## CLI Commands
334
+
335
+ ```bash
336
+ skilllite install # Install skillbox sandbox binary
337
+ skilllite uninstall # Remove skillbox binary
338
+ skilllite status # Show installation status
339
+ skilllite version # Show version information
340
+ skilllite mcp # Start MCP server
341
+ skilllite init-opencode # Initialize OpenCode integration
342
+ ```
343
+
291
344
  ## License
292
345
 
293
346
  MIT License
@@ -1,13 +1,19 @@
1
- skilllite/__init__.py,sha256=Ws0SEhXeqPTme-ASatdgkIba49zhZItN1Xlh2TVdhDY,3984
1
+ skilllite/__init__.py,sha256=D_6otb8PNLUqpxlsp_7WtLJlX7JDziOxGBc8iQhER9Q,3984
2
2
  skilllite/analyzer.py,sha256=YG-ZJ6bO59pkP8LGECjcyCDPpH6wUYOm5QGx6et-ItA,14504
3
3
  skilllite/builtin_tools.py,sha256=MGIlMlYTfj590PCY4ngbQteDIryfH7sDoKQyrDTH6L8,8067
4
- skilllite/cli.py,sha256=Ns_wjCWz3Lh67P6QB7HYe2gM94FG8mEwKvPnQIx9uqI,6209
5
4
  skilllite/quick.py,sha256=TNqKaww92WHUMf9nkjMZkdVjzz222lOKyjCwNp90_9c,16132
6
5
  skilllite/validation.py,sha256=K3Hn6VKT7aybdKX8GXSRaxLbKmyVAnMYmP9cqh-D4s4,4308
6
+ skilllite/cli/__init__.py,sha256=PATRR5ZeAoB9H4gzVgw_5VAiP1GybZOuiAa1rOKFdW0,611
7
+ skilllite/cli/__main__.py,sha256=69kpU78u45_64qSgv1v6x3m3xxaWjHwb-QdjbpxXZCo,152
8
+ skilllite/cli/binary.py,sha256=Yp8um1G33r9SFuiAJcJ41XaMotHuNKo08bCet-0jd0g,2264
9
+ skilllite/cli/main.py,sha256=cyLb6l7dDuvmu7ZCJhUsU9MeTzQo0qKLrnC75EKf5Sw,3983
10
+ skilllite/cli/mcp.py,sha256=Kp-Z6aRZ7NB3T-y-b56jx6AnRyPOkfzfFQmBxYAB23Q,767
11
+ skilllite/cli/integrations/__init__.py,sha256=-NWqUaDEPCRpykfDOvl2zfpUfLIH3UxG1qUbmTvb92o,120
12
+ skilllite/cli/integrations/opencode.py,sha256=wT7rCqOkmX9GdhI994-0HhUtZEJ3PELPOC9PRscQDls,10521
7
13
  skilllite/core/__init__.py,sha256=nU9yWE_M7xuWaIa1Ks_0dPfilRpW9GhlKfTV6eEU7-U,2044
8
14
  skilllite/core/executor.py,sha256=eNPcVKxKcLUjHJ7K2hstKtxobZiDIvPjk2IhbZv3nxo,6517
9
15
  skilllite/core/handler.py,sha256=pbQo5Gv-YSecufqpVbC0y8KP7Ra4uxxYpOW1nBimn5g,10354
10
- skilllite/core/loops.py,sha256=2LEIyhYbHAR5YqvkyQeoRLYJPbKoEPaBK_wSMo47wKI,32210
16
+ skilllite/core/loops.py,sha256=SQEhQolo2_yD3Vk5NAZ_3D1OPX0zpQ2N3SIIfay6Gao,32325
11
17
  skilllite/core/manager.py,sha256=uYAO5JtHM4DQvPnybBChHe0hUdOse1yBiQyc7vZ4nB8,19798
12
18
  skilllite/core/metadata.py,sha256=mdZdK-qfK7wSaZrAAn2U25k0I63NrYuibtBHIrYR5RA,11221
13
19
  skilllite/core/prompt_builder.py,sha256=rsubLzien9lKjOqM6iTn5nourPnz1-QQGzljiO0Z_3I,12844
@@ -16,7 +22,7 @@ skilllite/core/skill_info.py,sha256=4_jsArmn1e7jEUXDEkmDAzjSI_903sdpfNLHOp1t1-M,
16
22
  skilllite/core/tool_builder.py,sha256=k28ze50h7AjBiEfgKR6Y4DUFIGZJ1sCYbHEk_VkYl7w,12866
17
23
  skilllite/core/tools.py,sha256=3RiAnZ_7S7iE6ZHXTPNKmAYQFY8SBUEzcQJZA0tZmX8,8168
18
24
  skilllite/mcp/__init__.py,sha256=xrpoWy9zs8IJkW6fkkR4XjQrITo5PWvFW9NkmK8E2Fg,1028
19
- skilllite/mcp/server.py,sha256=vhM0Kgjul1DZ5XFFKjuWZ0iksFP4nWl9n_bpiaakpTM,27912
25
+ skilllite/mcp/server.py,sha256=3s7pYHZY3785QS120C1O6cF1RnVfrteEkhAGbJwDMHw,38091
20
26
  skilllite/sandbox/__init__.py,sha256=6LPisVXB94SJ-C34nBAxd05NGEZR4PPxz2xaDpX4qEk,918
21
27
  skilllite/sandbox/base.py,sha256=-Ul2QufoQ3myJNzBOEdv30A2HVSR9Oe4zEqpe9oJGqY,2628
22
28
  skilllite/sandbox/config.py,sha256=eA3ceou6jF4S5kptv6cmIGKynQTaGDx3_vRrkJGQaAE,8207
@@ -24,9 +30,9 @@ skilllite/sandbox/utils.py,sha256=o5AZVkhqIcZE9veUy7FTfN7uMHVWJJLYXs7gYIXsptg,25
24
30
  skilllite/sandbox/skillbox/__init__.py,sha256=SFnNlLnnAc_NEEkqOkDbxYNfXROL7n1S09K_HFI_BOE,866
25
31
  skilllite/sandbox/skillbox/binary.py,sha256=TNKQamWucMw-J6L5BjAasNpYqhBcBpHwQPMqjUOkgp4,11838
26
32
  skilllite/sandbox/skillbox/executor.py,sha256=dXCR8JMwjD-x7-LfOCQbvgSq-29ZLIkXLp4oHed7b1Y,22872
27
- skilllite-0.1.0.dist-info/licenses/LICENSE,sha256=ESBZ3GI5LkrbA2s5jZUQDqdcQ8oRlmk04aIYh34cOsw,1071
28
- skilllite-0.1.0.dist-info/METADATA,sha256=-uHBXQuqfXHjGqsf5Qc5MELgUe9Lq_0UPUwpTFsyeTQ,8190
29
- skilllite-0.1.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
30
- skilllite-0.1.0.dist-info/entry_points.txt,sha256=H-meSKN3XzctxMvY6fa-YjAWyzs9t_0IjujOfumn4Wk,84
31
- skilllite-0.1.0.dist-info/top_level.txt,sha256=aqo-9FEJuBbFT7WGE3aycTj27OxyQPCN6TuZtOcClAI,10
32
- skilllite-0.1.0.dist-info/RECORD,,
33
+ skilllite-0.1.1.dist-info/licenses/LICENSE,sha256=ESBZ3GI5LkrbA2s5jZUQDqdcQ8oRlmk04aIYh34cOsw,1071
34
+ skilllite-0.1.1.dist-info/METADATA,sha256=EaYy1mJx-5zAmdE8ZlFbn4cbhKNKV0QG2tBclCu9BV0,9984
35
+ skilllite-0.1.1.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
36
+ skilllite-0.1.1.dist-info/entry_points.txt,sha256=H-meSKN3XzctxMvY6fa-YjAWyzs9t_0IjujOfumn4Wk,84
37
+ skilllite-0.1.1.dist-info/top_level.txt,sha256=aqo-9FEJuBbFT7WGE3aycTj27OxyQPCN6TuZtOcClAI,10
38
+ skilllite-0.1.1.dist-info/RECORD,,