Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
149 changes: 149 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,151 @@
# mcp-remote-run

MCP Server to run code on a remote server and monitor it with CodeCarbon.

## Features

- **Code Injection**: Inject variables, dependencies, and code into Python scripts using AST manipulation
- **Temporary File Management**: Automatic cleanup of temporary files
- **Method Chaining**: Fluent API for multiple operations
- **Context Manager Support**: Use with `with` statement for automatic cleanup

## Installation

```bash
pip install mcp-remote-run
```

For development:

```bash
pip install -e ".[dev]"
```

## Usage

### Basic Variable Injection

```python
from mcp_remote_run import Injector

# Inject variables into code
with Injector(code="print(x + y)") as injector:
injector.inject_variables({"x": 10, "y": 20})
print(injector.get_code())
# Output:
# x = 10
# y = 20
# print(x + y)
```

### Adding Dependencies

```python
from mcp_remote_run import Injector

code = "import requests\nprint(requests.__version__)"
with Injector(code=code) as injector:
injector.add_dependency(["requests", "numpy"])
print(injector.get_code())
# Output:
# import os
# os.system("pip install requests numpy")
# import requests
# print(requests.__version__)
```

### Function Injection

```python
from mcp_remote_run import Injector

code = """
def calculate():
pass
"""

with Injector(code=code) as injector:
new_body = "return 42"
injector.inject_function(new_body, "calculate")
print(injector.get_code())
# Output:
# def calculate():
# return 42
```

### Method Chaining

```python
from mcp_remote_run import Injector

code = """
def process():
pass
"""

with Injector(code=code) as injector:
injector.inject_variables({"x": 5}) \
.add_dependency(["pandas"]) \
.inject_function("return x * 2", "process")

# Execute the modified code
temp_file = injector.get_temp_file_path()
# ... run temp_file remotely
```

### Working with Files

```python
from mcp_remote_run import Injector

# Load from file
injector = Injector(python_file_path="script.py")
injector.inject_variables({"api_key": "secret"})

# Get temporary file path for execution
temp_path = injector.get_temp_file_path()
print(f"Modified script at: {temp_path}")

# Clean up when done
injector.destroy()
```

## API Reference

### `Injector`

#### Constructor

```python
Injector(
python_file_path: str = None,
code: str = None,
module: cst.Module = None,
filename: str = "script.py"
)
```

- `python_file_path`: Path to existing Python file
- `code`: Python code as string
- `module`: Pre-parsed libcst Module object
- `filename`: Name for temporary file (when using `code` or `module`)

#### Methods

- `inject_variables(variables: Dict[str, Any])`: Inject variable assignments
- `add_dependency(packages: list)`: Add pip install commands
- `inject_function(code: str, func_name: str)`: Replace function body
- `get_code()`: Get modified code as string
- `get_temp_file_path()`: Get path to temporary file
- `get_temp_dir()`: Get path to temporary directory
- `destroy()`: Clean up temporary files

## Testing

```bash
pytest tests/ -v
```

## License

MIT License - see LICENSE file for details.
154 changes: 154 additions & 0 deletions examples/basic_usage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
"""
Example usage of the Injector class for code injection and modification.
"""

from mcp_remote_run import Injector


def example_variable_injection():
"""Demonstrate variable injection"""
print("=== Example 1: Variable Injection ===")

code = """
def main():
print(f"x = {x}, y = {y}")
print(f"Sum: {x + y}")

if __name__ == "__main__":
main()
"""

with Injector(code=code) as injector:
injector.inject_variables({"x": 10, "y": 20})
print("Modified code:")
print(injector.get_code())
print()


def example_dependency_injection():
"""Demonstrate dependency injection"""
print("=== Example 2: Dependency Injection ===")

code = """
import json

def process_data():
data = {"message": "Hello, World!"}
print(json.dumps(data))

if __name__ == "__main__":
process_data()
"""

with Injector(code=code) as injector:
injector.add_dependency(["requests", "pandas"])
print("Modified code:")
print(injector.get_code())
print()


def example_function_injection():
"""Demonstrate function body injection"""
print("=== Example 3: Function Injection ===")

code = """
def calculate():
pass

def main():
result = calculate()
print(f"Result: {result}")

if __name__ == "__main__":
main()
"""

with Injector(code=code) as injector:
new_body = """
a = 10
b = 20
return a * b
"""
injector.inject_function(new_body.strip(), "calculate")
print("Modified code:")
print(injector.get_code())
print()


def example_chaining():
"""Demonstrate method chaining"""
print("=== Example 4: Method Chaining ===")

code = """
def process():
pass

if __name__ == "__main__":
result = process()
print(f"Result: {result}")
"""

with Injector(code=code) as injector:
injector.inject_variables({"multiplier": 5}) \
.add_dependency(["numpy"]) \
.inject_function("return multiplier * 10", "process")

print("Modified code:")
print(injector.get_code())
print()


def example_complete_workflow():
"""Demonstrate a complete workflow"""
print("=== Example 5: Complete Workflow ===")

code = """
def run_experiment():
pass

if __name__ == "__main__":
run_experiment()
"""

with Injector(code=code) as injector:
# Step 1: Inject configuration variables
config = {
"epochs": 10,
"batch_size": 32,
"learning_rate": 0.001
}
injector.inject_variables(config)

# Step 2: Add required dependencies
injector.add_dependency(["codecarbon", "torch"])

# Step 3: Inject experiment code
experiment_code = """
from codecarbon import EmissionsTracker

tracker = EmissionsTracker()
tracker.start()

# Simulate training
for epoch in range(epochs):
print(f"Epoch {epoch + 1}/{epochs}")
print(f"Batch size: {batch_size}, LR: {learning_rate}")

tracker.stop()
print("Experiment complete!")
"""
injector.inject_function(experiment_code.strip(), "run_experiment")

print("Final modified code:")
print(injector.get_code())
print()
print(f"Temporary file created at: {injector.get_temp_file_path()}")
print("This file can be sent to a remote server for execution.")


if __name__ == "__main__":
example_variable_injection()
example_dependency_injection()
example_function_injection()
example_chaining()
example_complete_workflow()
43 changes: 43 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
[build-system]
requires = ["setuptools>=45", "wheel", "setuptools_scm[toml]>=6.2"]
build-backend = "setuptools.build_meta"

[project]
name = "mcp-remote-run"
version = "0.1.0"
description = "MCP Server to run code on a remote server and monitor it with CodeCarbon"
readme = "README.md"
requires-python = ">=3.8"
license = {text = "MIT"}
authors = [
{name = "MLCo2", email = "contact@mlco2.org"}
]
classifiers = [
"Development Status :: 3 - Alpha",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
]
dependencies = [
"libcst>=1.0.0",
]

[project.optional-dependencies]
dev = [
"pytest>=7.0.0",
"pytest-cov>=4.0.0",
]

[tool.setuptools]
packages = ["mcp_remote_run"]
package-dir = {"" = "src"}

[tool.pytest.ini_options]
testpaths = ["tests"]
python_files = ["test_*.py"]
python_classes = ["Test*"]
python_functions = ["test_*"]
6 changes: 6 additions & 0 deletions src/mcp_remote_run/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
"""MCP Remote Run - MCP Server to run code on a remote server and monitor it with CodeCarbon."""

from .injector import Injector

__version__ = "0.1.0"
__all__ = ["Injector"]
Loading