PCTX Framework

repository·main·Indexed 42 days ago

https://github.com/portofcontext/pctx

An open-source framework that connects AI agents to tools and the Model Context Protocol (MCP) via 'Code Mode'. It enables agents to execute TypeScript code in a secure sandboxed Deno runtime instead of sequential tool calls, reducing token consumption. Features include a Rust-based execution engine (CodeMode), a Python client (pctx-client v0.4.5), support for JS Local Tools, and network permission controls via AllowedHosts.

Tokens
61.4K
Snippets
213
Records
276
Agent score
54%

What's inside PCTX

  1. What is Code Mode in pctx?

    main

    Code Mode is a feature of pctx that allows AI agents to write and execute TypeScript code in a sandboxed environment instead of making sequential, high-token tool calls.

    In traditional MCP, data is passed back and forth between the tool and the model's context window, leading to high token usage. In Code Mode, the AI writes a single TypeScript block that performs logic (like filter, map, or reduce) locally within the execution environment. This results in approximately 98% token reduction because the raw data stays in the sandbox and only the results or stdout are returned to the model.

    // Data stays in execution environment, process with native JS
    const items = await myserver.getItems();
    const filtered = items.filter((x) => x.status === "pending");
    await myserver.updateItems(filtered.map((x) => ({ ...x, processed: true })));
  2. What is Code Mode and how does it work?

    main

    Code Mode allows AI agents to execute TypeScript code that has access to your custom Python tools and MCP servers. Instead of making individual tool calls, the agent writes and executes TypeScript code to orchestrate multiple function calls, process data, and return results in a single execution step.

    This is achieved by running the code in an isolated Deno sandbox. The agent discovers available functions (from your tools or MCP servers) and invokes them using namespaced TypeScript calls (e.g., await MyNamespace.myFunction(...)).

  3. Manage tool timeouts and queue wait times

    main

    When using concurrency controls or rate limiting, you must account for the fact that the server's tool_timeout_secs timer starts at dispatch, not at the moment the tool actually begins execution.

    The Timeout Constraint

    If a tool is queued (due to a concurrency cap or rate limiting), the time spent waiting in the queue consumes the tool's timeout budget.

    Important: When configuring tool_timeout_secs, ensure the value covers both the expected queue wait time and the actual tool execution duration. If the timeout is too short, tools may fail with a timeout error before they even start running.

  4. How Code Mode execution works

    main

    The Code Mode workflow follows these steps:

    1. Discovery: The AI calls pctx.list_functions() to find available namespaces.
    2. Inspection: The AI calls pctx.get_function_details(['namespace.function']) to get TypeScript signatures and JSDoc.
    3. Authoring: The AI writes TypeScript code using the discovered functions.
    4. Validation: pctx type-checks the code using typescript-go (providing feedback in < 100ms).
    5. Execution: If types pass, pctx executes the code in a Deno sandbox.
    6. Result: The AI receives the stdout and the function return value.
  5. pctx Security Model

    main

    pctx provides a secure environment for LLM-generated code:

    • Isolation: Code runs in an isolated Deno sandbox.
    • Network Control: The sandbox can only access network hosts explicitly specified in the configuration file.
    • Restricted Access: There is no filesystem, environment, or system access by default.
    • Auth Privacy: MCP clients are authenticated within pctx. The LLM never sees your authentication secrets.
  6. Understand how large execution payloads are handled

    main

    When using execute_code, responses are sent over a WebSocket. Because these responses are serialized into a single WebSocket text frame, they are subject to a default 16 MiB limit enforced by the receiver.

    Large payloads typically occur due to:

    1. Trace bloat: Tool call outputs are cloned into trace.events[].outcome.output. Large tool results can cause the entire trace to exceed 16 MiB.
    2. Large return values or stdout: The script itself produces a massive return value or prints excessive data to stdout.

    Current Behavior (Lossy Truncation): To prevent silent failures, the system currently uses truncate::bound_response_size to shrink oversized responses under a 15 MiB ceiling. If a response is too large, the system will:

    • Drop the trace data.
    • Replace the return value with a marker: { "__truncated__": true, "reason": ... }.
    • Append the reason for truncation to stderr so the calling agent is aware that data was lost.
  7. How Code Mode works

    main

    Code Mode is a paradigm shift from sequential tool calling to code execution. Instead of an agent calling multiple tools one-by-one and passing large amounts of data through its context window, Code Mode allows the agent to write code that executes in a sandbox. This significantly reduces token usage by performing data processing (like filtering or aggregating) within the execution environment rather than in the LLM context.

    Example Comparison:

    Traditional MCP flow (High Token Usage):

    1. Agent calls getSheet(id)
    2. Server returns 1000 rows $\rightarrow$ agent's context
    3. Agent calls filterRows(criteria)
    4. Server returns 50 rows $\rightarrow$ agent's context

    With Code Mode (Low Token Usage):

    const sheet = await gdrive.getSheet({ sheetId: "abc" });
    const orders = sheet.filter((row) => row.status === "pending");
    console.log(`Found ${orders.length} orders`);

    This can result in a massive reduction in tokens (e.g., 150k $\rightarrow$ 2k).

  8. How CodeMode works as an execution engine

    main

    The CodeMode struct is the central engine for executing TypeScript code in a sandboxed Deno runtime. It manages the lifecycle of tools (MCP servers and callbacks) and provides methods to inspect available functions or execute code.

    Key Capabilities

    Configuration (Builder/Mutable methods):

    • with_server() / with_servers(): Add MCP servers (Builder pattern).
    • with_callback() / with_callbacks(): Add callback tools (Builder pattern).
    • add_server() / add_servers(): Add MCP servers (Mutable).
    • add_callback() / add_callbacks(): Add callback tools (Mutable).
    • add_tool_set(): Add a pre-built ToolSet directly.

    Inspection:

    • list_functions(): Returns a list of all available functions with minimal interfaces.
    • get_function_details(GetFunctionDetailsInput): Returns full typed TypeScript interfaces for specific functions.
    • tool_sets(), server_tool_sets(), servers(), callbacks(): Accessors for registered configurations.

    Execution:

    • execute_typescript(code, disclosure, registry): Runs TypeScript code in the sandbox.
    • execute_bash(command): Runs a bash command within the virtual filesystem.
    use pctx_code_mode::CodeMode;
    use pctx_code_mode::model::{CallbackConfig, GetFunctionDetailsInput, FunctionId};
    use serde_json::json;
    
    let mut code_mode = CodeMode::default();
    
    // Add callback tools
    code_mode.add_callback(&CallbackConfig {
        namespace: "DataApi".to_string(),
        name: "fetchData".to_string(),
        description: Some("Fetches data from API".to_string()),
        input_schema: Some(json!({
            "type": "object",
            "properties": { "id": { "type": "integer" } },
            "required": ["id"]
        })),
        output_schema: None,
    })?;
    
    // List available functions
    let list = code_mode.list_functions();
    for func in list.functions {
        println!("{}.{}: {:?}", func.namespace, func.name, func.description);
    }
    
    // Get detailed type information
    let details = code_mode.get_function_details(GetFunctionDetailsInput {
        functions: vec![
            FunctionId { mod_name: "DataApi".into(), fn_name: "fetchData".into() }
        ],
    });
    println!("TypeScript definitions:\n{}", details.code);
  9. How JS Local Tools work with dependencies

    main

    JS Local Tools allow you to define JavaScript callbacks in a 'trusted zone' where you have access to full dependencies (like npm:zod or database clients). Sandboxed code can then invoke these tools via callJsLocalTool without needing access to those dependencies itself. This pattern is ideal for building a TypeScript SDK where the user defines complex, validated tools that run in a restricted environment.

    // In the trusted zone (with dependencies)
    import { z } from 'npm: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);
    });
    
    // In the sandboxed code (no Zod access needed)
    const user = await callJsLocalTool("createUser", {
        name: "Alice",
        email: "alice@example.com",
        age: 30
    });
  10. Use server names as TypeScript namespaces

    main

    The name field in a server configuration is converted to camelCase and used as the TypeScript namespace for accessing that server's tools.

    Requirements:

    • Must be unique within the configuration.
    • Should be a valid TypeScript identifier (alphanumeric, underscores, no spaces).
    • Keep it short and descriptive.

    Example Mapping:

    • Server name "g_drive" $\rightarrow$ await gDrive.getSheet(...)
    • Server name "slack" $\rightarrow$ await slack.sendMessage(...)
  11. Configure Stdio Server command parsing

    main

    When configuring stdio servers in your configuration, you can define the execution method in two ways:

    Option 1: Shell-style command string

    Provide a single string in the command field. pctx will automatically parse this into an executable and arguments using shell-style parsing.

    Option 2: Explicit command and args

    Provide a command string and an args array. This is recommended if your command has complex quoting requirements, if you want to be explicit about argument boundaries, or if you are generating configuration programmatically.

    Important: If both command contains spaces AND args is provided, the args array takes precedence and no shell-style parsing is performed.

    // Option 1: Shell-style
    {
      "name": "memory",
      "command": "npx -y @modelcontextprotocol/server-memory"
    }
    
    // Option 2: Explicit args
    {
      "name": "memory",
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-memory"]
    }
  12. Sandboxed Execution constraints in Code Mode

    main

    Code written in Code Mode runs in a Deno sandbox with the following strict security limits:

    • Timeout: 10-second maximum execution time.
    • Filesystem/Env: No access to the host filesystem or environment variables.
    • Network: Restricted to configured MCP hosts only.
    • Authentication: Uses pre-authenticated MCP clients; the AI agent never sees the underlying credentials.