pctx_code_execution_runtime 0.1.0

JavaScript/TypeScript execution runtime for pctx
# PCTX Code Execution Runtime

A Deno extension providing:
- **MCP Client**: Model Context Protocol client functionality
- **JS Local Tools**: JavaScript callback-based tools with dependency support
- **Console Capturing**: Automatic stdout/stderr capture
- **Network Permissions**: Host-based fetch controls

## Primary Use Case: TypeScript SDK with Dependencies

The main use case is running PCTX as a TypeScript SDK where users define tools with their own dependencies:

```javascript
// User has Zod in their environment
import { z } from 'npm:zod';

// Define a tool that uses Zod
registerJsLocalTool({
    name: "createUser",
    description: "Creates a user with validation"
}, (args) => {
    const UserSchema = z.object({
        name: z.string().min(2),
        email: z.string().email(),
        age: z.number().min(0).max(120)
    });
    return UserSchema.parse(args); // Uses Zod for validation!
});

// Sandboxed code calls the tool (doesn't need Zod directly)
const user = await callJsLocalTool("createUser", {
    name: "Alice",
    email: "alice@example.com",
    age: 30
});
```

**Key benefit**: Dependencies (Zod, database clients, APIs, etc.) are available where tools are **defined** (trusted zone), but sandboxed code just calls the tools without needing dependency access.

See the complete example: [typescript_sdk_with_dependencies.rs](examples/typescript_sdk_with_dependencies.rs)

## Quick Start

```rust
use deno_core::{JsRuntime, RuntimeOptions};
use pctx_code_execution_runtime::{
    pctx_runtime_snapshot, MCPRegistry, JsCallableToolRegistry,
    AllowedHosts, RUNTIME_SNAPSHOT
};

// Create registries
let mcp_registry = MCPRegistry::new();
let js_local_tool_registry = JsCallableToolRegistry::new();
let allowed_hosts = AllowedHosts::new(Some(vec!["example.com".to_string()]));

let mut runtime = JsRuntime::new(RuntimeOptions {
    startup_snapshot: Some(RUNTIME_SNAPSHOT),
    extensions: vec![pctx_runtime_snapshot::init(
        mcp_registry,
        js_local_tool_registry,
        allowed_hosts
    )],
    ..Default::default()
});

// MCP API and JS Local Tools are now available in JavaScript
let code = r#"
    // Register an MCP server
    registerMCP({ name: "my-server", url: "http://localhost:3000" });

    // Register a local tool with a callback
    registerJsLocalTool({
        name: "calculator",
        description: "Performs arithmetic",
    }, (args) => {
        return args.a + args.b;
    });

    // Call MCP tool
    const mcpResult = await callMCPTool({
        name: "my-server",
        tool: "get_data",
        arguments: { id: 42 }
    });

    // Call local tool
    const localResult = await callJsLocalTool("calculator", { a: 10, b: 5 });

    console.log("MCP:", mcpResult, "Local:", localResult);
"#;

runtime.execute_script("<main>", code)?;
```

## Rust API Reference

### Core Types

#### `MCPRegistry`

Thread-safe registry for MCP server configurations.

```rust
let registry = MCPRegistry::new();
```

#### `JsCallableToolRegistry`

Thread-safe registry for local tool metadata (callbacks stored in JS).

```rust
let callable_registry = JsCallableToolRegistry::new();

// Query tools
if callable_registry.has("my-tool") {
    println!("Tool exists!");
}

let metadata = callable_registry.get_metadata("my-tool");
let all_tools = callable_registry.list();
```

#### `AllowedHosts`

Whitelist of hosts allowed for network access.

```rust
let allowed_hosts = AllowedHosts::new(Some(vec![
    "example.com".to_string(),
    "api.service.com".to_string(),
]));
```

### Snapshot

#### `RUNTIME_SNAPSHOT`

Pre-compiled V8 snapshot containing the runtime.

```rust
pub static RUNTIME_SNAPSHOT: &[u8] = /* ... */;
```

## Examples

### Console Output Capture

```rust
let code = r#"
    console.log("Line 1");
    console.log("Line 2");
    console.error("Error line");

    export default {
        stdout: globalThis.__stdout,
        stderr: globalThis.__stderr
    };
"#;

let result = runtime.execute_script("<capture>", code)?;

// Extract captured output
let scope = &mut runtime.handle_scope();
let local = v8::Local::new(scope, result);
let output = serde_v8::from_v8::<serde_json::Value>(scope, local)?;

println!("Stdout: {:?}", output["stdout"]);
println!("Stderr: {:?}", output["stderr"]);
```

### Network Permissions

```rust
// Allow only specific hosts
let allowed_hosts = AllowedHosts::new(Some(vec![
    "api.example.com".to_string(),
    "cdn.example.com".to_string(),
]));

let mut runtime = JsRuntime::new(RuntimeOptions {
    startup_snapshot: Some(RUNTIME_SNAPSHOT),
    extensions: vec![pctx_runtime_snapshot::init(
        MCPRegistry::new(),
        allowed_hosts
    )],
    ..Default::default()
});

let code = r#"
    // This will succeed
    await fetch("http://api.example.com/data");

    // This will fail - host not allowed
    try {
        await fetch("http://malicious.com/data");
    } catch (e) {
        console.error("Blocked:", e.message);
    }
"#;

runtime.execute_script("<permissions>", code)?;
```

## Security

### Network Access

- Only whitelisted hosts can be accessed via `fetch()`
- Attempts to access non-whitelisted hosts throw errors
- Host matching is exact (no wildcards)

### MCP Registry

- Each runtime instance has its own isolated registry
- No cross-runtime access to MCP configurations
- Registry is not persisted between runtime sessions

### Console Capture

- Captured output is stored in runtime-local buffers
- No disk I/O or external logging
- Buffers cleared when runtime is dropped

## Performance

- **Startup**: Instant (V8 snapshot loads in <1ms)
- **Memory**: ~2MB base runtime overhead
- **MCP Operations**: Native Rust performance
- **Console Capture**: Minimal overhead (~1% per log)

## License

MIT

## Contributing

Contributions welcome! Please ensure:

- All tests pass: `cargo test --package pctx_runtime`
- Code is formatted: `cargo fmt`
- Documentation is updated

## Features

### JS Local Tools

Local tools allow you to define JavaScript callbacks that can be invoked from sandboxed code. See [JS_LOCAL_TOOLS.md](JS_LOCAL_TOOLS.md) for comprehensive documentation.

**Quick example:**

```javascript
// Register a tool with a callback
registerJsLocalTool({
    name: "file-reader",
    description: "Reads a file from the host system",
    inputSchema: {
        type: "object",
        properties: {
            path: { type: "string" }
        }
    }
}, async (args) => {
    const fs = require('fs').promises;
    return await fs.readFile(args.path, 'utf8');
});

// Call the tool from sandboxed code
const content = await callJsLocalTool("file-reader", { path: "./data.txt" });
```

**Benefits:**
- Fast: No IPC overhead
- Simple: Just JavaScript callbacks
- Flexible: Sync or async
- Type-safe: Full TypeScript support

### MCP Client

Connect to external MCP servers for tool integration.

```javascript
registerMCP({
    name: "github",
    url: "http://localhost:3000"
});

const result = await callMCPTool({
    name: "github",
    tool: "create_issue",
    arguments: { title: "Bug report" }
});
```

## Examples

See the [examples/](examples/) directory:
- **[typescript_sdk_with_dependencies.rs]examples/typescript_sdk_with_dependencies.rs** - **Primary use case**: Shows how TypeScript SDK users can define tools with custom dependencies (Zod, DB clients, etc.) and use them from sandboxed code

## See Also

- [`pctx_type_check`]../pctx_type_check - TypeScript type checking runtime
- [`pctx_executor`]../pctx_executor - Complete TypeScript execution environment
- [Model Context Protocol]https://modelcontextprotocol.io - MCP specification
- [JS_LOCAL_TOOLS.md]JS_LOCAL_TOOLS.md - Detailed JS local tools documentation