Skip to content
Merged
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
47 changes: 47 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,53 @@ computer.cpp app serve ./reminders.lua --listen 127.0.0.1:8787
POST /mcp
```

### Multiple Lua App Servers

The desktop tray can run every configured Lua app at the same time. Each app
gets its own `computer.cpp app serve` process and port, so it can be restarted
or stopped without affecting the others.

Configure apps in **Settings > Server**. A blank app port is assigned
automatically from `base_port`; a fixed port is reserved for that app and
cannot be reused by another configured app. Automatic assignment checks at
most 100 consecutive ports beginning at `base_port` (and never past 65535).

```toml
[server]
host = "127.0.0.1"
base_port = 8787

[server.apps.notes]
display_name = "Notes"
path = "/absolute/path/to/notes.lua"
port = 8787

[server.apps.reminders]
display_name = "Reminders"
path = "/absolute/path/to/reminders.lua"
# No port: choose the first available non-reserved port from 8787.
```

The tray menu shows the number of running servers, one-click **Start All
Servers** and **Stop All Servers** actions, and a Start/Stop row for each app.
Servers are not started automatically when ComputerCpp launches. Any healthy
configured servers left by an interrupted tray process are adopted, and
quitting ComputerCpp stops all managed servers.

Server processes keep independent Lua memory and operation storage, while
top-level app commands share one desktop-control queue. If Notes and Reminders
receive commands at the same time, one command holds exclusive mouse and
keyboard control for its entire workflow and the other waits. The lease is
renewed while a long command runs and released on success or failure. Health,
schema, and operation-status requests do not acquire desktop control.

With the example above, the MCP endpoints could be:

```text
http://127.0.0.1:8787/mcp # Notes
http://127.0.0.1:8788/mcp # Reminders
```

The MCP server turns a Lua app definition into app-level tools such as:

```text
Expand Down
13 changes: 13 additions & 0 deletions include/computer_cpp/AppConfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
#include <nlohmann/json.hpp>

#include <filesystem>
#include <functional>
#include <map>
#include <optional>
#include <set>
#include <string>
#include <vector>

Expand Down Expand Up @@ -44,6 +46,17 @@ struct ServerConfig {
std::map<std::string, ServerAppConfig> apps;
};

struct ServerPortPlan {
std::map<std::string, int> ports;
std::map<std::string, std::string> errors;
};

ServerPortPlan PlanServerPorts(
const ServerConfig& server,
const std::set<std::string>& appNames,
const std::set<int>& occupiedPorts,
const std::function<bool(int)>& portAvailable);

struct RecordingConfig {
bool enabled = false;
int retentionDays = 14;
Expand Down
6 changes: 6 additions & 0 deletions include/computer_cpp/TrayServerState.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include <filesystem>
#include <optional>
#include <string>
#include <vector>

namespace ComputerCpp {

Expand All @@ -13,11 +14,16 @@ struct TrayAppServerState {
std::string url;
std::string appPath;
std::string appId;
std::string configName;
std::string displayName;
std::string startedAt;
};

// Legacy single-server state path. Kept for migration from older releases.
std::filesystem::path TrayAppServerStatePath();
std::filesystem::path TrayAppServerStateDirectory();
std::filesystem::path TrayAppServerStatePath(const std::string& configName);
std::vector<std::filesystem::path> ListTrayAppServerStatePaths(std::string* error = nullptr);
bool SaveTrayAppServerState(const TrayAppServerState& state, const std::filesystem::path& path, std::string* error = nullptr);
std::optional<TrayAppServerState> LoadTrayAppServerState(const std::filesystem::path& path, std::string* error = nullptr);
bool RemoveTrayAppServerState(const std::filesystem::path& path, std::string* error = nullptr);
Expand Down
Loading
Loading