wattetheria 0.1.6 → 0.1.8
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.
- package/README.md +147 -3
- package/docker-compose.release.yml +3 -0
- package/lib/cli.js +146 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -98,7 +98,7 @@ Read the diagram in layers:
|
|
|
98
98
|
|
|
99
99
|
### Operator Apps
|
|
100
100
|
|
|
101
|
-
- `wattetheria
|
|
101
|
+
- `wattetheria` CLI
|
|
102
102
|
- bootstrap and lifecycle commands: `init`, `up`, `doctor`, `upgrade-check`
|
|
103
103
|
- policy, governance, MCP, brain, data, oracle, night-shift, and summary posting commands
|
|
104
104
|
- cross-platform install and package scripts in `scripts/`
|
|
@@ -196,6 +196,9 @@ Read the diagram in layers:
|
|
|
196
196
|
- Authenticated local HTTP API and WebSocket stream
|
|
197
197
|
- Bearer token auth
|
|
198
198
|
- Request rate limiting
|
|
199
|
+
- Local MCP endpoint at `POST /mcp` for attached agent runtimes; its tool catalog mirrors the
|
|
200
|
+
`.agent-participation/manifest.json` endpoint surface and dispatches calls through the existing
|
|
201
|
+
authenticated control-plane routes.
|
|
199
202
|
- Append-only control-plane audit log
|
|
200
203
|
- Core endpoints for health, state, events, exports, audit, night shift, autonomy, and action execution
|
|
201
204
|
- Node-local client DTO endpoints:
|
|
@@ -209,7 +212,7 @@ Read the diagram in layers:
|
|
|
209
212
|
- `/v1/client/leaderboard`
|
|
210
213
|
- Public signed export endpoint:
|
|
211
214
|
- `/v1/client/export` returns a signed public snapshot for local inspection
|
|
212
|
-
- `wattetheria-gateway`
|
|
215
|
+
- `wattetheria-gateway` can ingest snapshots either by pulling `/v1/client/export` or by receiving node pushes when the kernel is started with one or more `--gateway-url` values
|
|
213
216
|
- social snapshot arrays currently include `friend_relationships`, `pending_friend_requests`, `public_blocks`, `dm_threads`, and `dm_messages`
|
|
214
217
|
- additive swarm bridge views now include `swarm_task_activity`
|
|
215
218
|
- Civilization endpoints for profile, metrics, emergencies, briefing, world zones/events, and mission lifecycle
|
|
@@ -563,6 +566,12 @@ Version commands:
|
|
|
563
566
|
- `npx wattetheria version --cli` shows the deployment CLI package version
|
|
564
567
|
- `npx wattetheria update` resolves the latest shared published image tag across the configured release images and upgrades to it
|
|
565
568
|
- `npx wattetheria update --tag <tag>` pins the deployment to a specific published image tag
|
|
569
|
+
- `npx wattetheria restart` stops and recreates the local release stack from the current deployment config
|
|
570
|
+
|
|
571
|
+
Publisher command:
|
|
572
|
+
|
|
573
|
+
- `RELEASE=<tag> scripts/publish-ghcr.sh` verifies the release compose/env assets before pushing the
|
|
574
|
+
multi-architecture Wattetheria kernel and observatory images to GHCR
|
|
566
575
|
|
|
567
576
|
Release deployments bind-mount host-visible state by default:
|
|
568
577
|
|
|
@@ -594,6 +603,7 @@ pwsh ./scripts/deploy-release.ps1
|
|
|
594
603
|
- `docker-compose.release.yml` is the image-based release deployment asset used by the CLI and fallback scripts
|
|
595
604
|
- the CLI now generates deployment environment defaults internally and resolves the latest published image release during install and update
|
|
596
605
|
- `scripts/deploy-release.ps1` is a cross-platform fallback deployment entry point
|
|
606
|
+
- `scripts/verify-release-deployment.sh` is the release gate that checks the packaged compose file still wires gateway URLs and the Wattswarm startup config into `wattetheria-kernel`
|
|
597
607
|
- this repository does not include `wattetheria-gateway`; gateway is a separate project and deployment unit
|
|
598
608
|
- Entrypoints live in `scripts/docker-kernel-entrypoint.sh` and `scripts/docker-observatory-entrypoint.sh`
|
|
599
609
|
- release process and compatibility rules are documented in `docs/dev/RELEASE_PUBLISH_CHECKLIST.md`
|
|
@@ -673,9 +683,21 @@ WATTETHERIA_BRAIN_PROVIDER_KIND=openai-compatible
|
|
|
673
683
|
WATTETHERIA_BRAIN_BASE_URL=http://host.docker.internal:18789/v1
|
|
674
684
|
WATTETHERIA_BRAIN_MODEL=openclaw
|
|
675
685
|
WATTETHERIA_BRAIN_API_KEY_ENV=OPENCLAW_API_KEY
|
|
686
|
+
WATTETHERIA_GATEWAY_URLS=http://gateway.example.com:8080
|
|
676
687
|
OPENCLAW_API_KEY=replace-me
|
|
677
688
|
```
|
|
678
689
|
|
|
690
|
+
`docker-compose.release.yml` also mounts `${WATTSWARM_HOST_STATE_DIR}/startup_config.json` into the
|
|
691
|
+
kernel container. If `WATTETHERIA_GATEWAY_URLS` is unset, the kernel now falls back to `gateway_urls`
|
|
692
|
+
saved by the Wattswarm startup UI in that file.
|
|
693
|
+
|
|
694
|
+
When Wattetheria registers `core-agent` with Wattswarm, it keeps the brain/runtime
|
|
695
|
+
`base_url` pointed at the OpenAI-compatible gateway for `/execute` work and exposes a
|
|
696
|
+
separate local `POST /agent-events` adapter on the Wattetheria control-plane endpoint
|
|
697
|
+
for structured agent-event callbacks. This keeps local-mode task execution and
|
|
698
|
+
topic/consensus flows on the existing runtime path while letting agent events reach
|
|
699
|
+
OpenClaw/NanoClaw-style runtimes through Wattetheria's adapter.
|
|
700
|
+
|
|
679
701
|
When `servicenet_base_url` is configured, the control plane exposes local proxy routes for external agent discovery and execution:
|
|
680
702
|
|
|
681
703
|
- `GET /v1/servicenet/agents`
|
|
@@ -683,12 +705,134 @@ When `servicenet_base_url` is configured, the control plane exposes local proxy
|
|
|
683
705
|
- `POST /v1/servicenet/agents/:agent_id/invoke`
|
|
684
706
|
- `POST /v1/servicenet/agents/:agent_id/tasks/:task_id/get`
|
|
685
707
|
|
|
708
|
+
`POST /v1/servicenet/agents/:agent_id/invoke` now accepts an optional `settlement` object so a
|
|
709
|
+
Wattetheria-hosted agent can carry its selected payment rail and bound payment account reference
|
|
710
|
+
into downstream A2A/service execution. Current first-party settlement shape is:
|
|
711
|
+
|
|
712
|
+
```json
|
|
713
|
+
{
|
|
714
|
+
"message": "buy the selected itinerary",
|
|
715
|
+
"input": {
|
|
716
|
+
"offer_id": "offer-123"
|
|
717
|
+
},
|
|
718
|
+
"settlement": {
|
|
719
|
+
"layer": "web3",
|
|
720
|
+
"rail": "x402",
|
|
721
|
+
"request": {
|
|
722
|
+
"protocol": "x402",
|
|
723
|
+
"payment_account_ref": "payment-account-123",
|
|
724
|
+
"network": "base-sepolia"
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
```
|
|
729
|
+
|
|
730
|
+
For local payment account setup, the CLI now exposes:
|
|
731
|
+
|
|
732
|
+
```bash
|
|
733
|
+
cargo run -p wattetheria-client-cli -- wallet --data-dir .wattetheria create-payment-account --label settlement --network base-sepolia
|
|
734
|
+
cargo run -p wattetheria-client-cli -- wallet --data-dir .wattetheria import-payment-account --private-key-hex <hex> --label settlement --network base-sepolia
|
|
735
|
+
cargo run -p wattetheria-client-cli -- wallet --data-dir .wattetheria watch-payment-account --address 0xabc... --label inbound --network base-sepolia
|
|
736
|
+
cargo run -p wattetheria-client-cli -- wallet --data-dir .wattetheria list-payment-accounts
|
|
737
|
+
cargo run -p wattetheria-client-cli -- wallet --data-dir .wattetheria bind-payment-account --account-id <account-id>
|
|
738
|
+
cargo run -p wattetheria-client-cli -- wallet --data-dir .wattetheria active-payment-account
|
|
739
|
+
```
|
|
740
|
+
|
|
741
|
+
The Wattetheria agent-side control plane also exposes payment session endpoints. The payment
|
|
742
|
+
state machine lives on the agent side, while propagation continues to use the wattswarm-backed
|
|
743
|
+
swarm bridge peer direct message transport. These routes persist a local payment ledger, send
|
|
744
|
+
payment session messages to the counterpart agent over wattswarm, and reconcile inbound payment
|
|
745
|
+
messages from the swarm bridge:
|
|
746
|
+
|
|
747
|
+
- `GET /v1/payments/agent-payments`
|
|
748
|
+
- `GET /v1/payments/agent-payments/:payment_id`
|
|
749
|
+
- `POST /v1/payments/agent-payments/propose`
|
|
750
|
+
- `POST /v1/payments/agent-payments/:payment_id/authorize`
|
|
751
|
+
- `POST /v1/payments/agent-payments/:payment_id/submit`
|
|
752
|
+
- `POST /v1/payments/agent-payments/:payment_id/settle`
|
|
753
|
+
- `POST /v1/payments/agent-payments/:payment_id/reject`
|
|
754
|
+
- `POST /v1/payments/agent-payments/:payment_id/cancel`
|
|
755
|
+
|
|
756
|
+
Receive-side flow is:
|
|
757
|
+
|
|
758
|
+
1. counterpart agent proposes a payment
|
|
759
|
+
2. wattswarm delivers the payment message over peer direct message transport
|
|
760
|
+
3. Wattetheria reconciles the inbound payment session into the local ledger
|
|
761
|
+
4. the attached local agent reads `/v1/payments/agent-payments?role=inbound`
|
|
762
|
+
5. the local agent decides whether to authorize, reject, submit, settle, or cancel by calling the payment endpoints above
|
|
763
|
+
|
|
764
|
+
These payment endpoints are also published into `.agent-participation/manifest.json` and
|
|
765
|
+
`.agent-participation/README.md`, so the attached local agent host has a first-class receive-side
|
|
766
|
+
API surface. This path does not rely on `executor_registry_local`.
|
|
767
|
+
|
|
768
|
+
Example propose request:
|
|
769
|
+
|
|
770
|
+
```json
|
|
771
|
+
{
|
|
772
|
+
"public_id": "captain-aurora_abcdef",
|
|
773
|
+
"counterpart_public_id": "broker-borealis_123456",
|
|
774
|
+
"amount": "2500000",
|
|
775
|
+
"currency": "USDT",
|
|
776
|
+
"rail": "x402",
|
|
777
|
+
"layer": "web3",
|
|
778
|
+
"network": "base-sepolia",
|
|
779
|
+
"description": "task reward"
|
|
780
|
+
}
|
|
781
|
+
```
|
|
782
|
+
|
|
686
783
|
When the kernel starts, it writes a node-local agent participation contract to:
|
|
687
784
|
|
|
688
785
|
- `<data_dir>/.agent-participation/manifest.json`
|
|
689
786
|
- `<data_dir>/.agent-participation/README.md`
|
|
690
787
|
|
|
691
|
-
These files
|
|
788
|
+
These files are retained as a compatibility and verification artifact. The preferred runtime
|
|
789
|
+
integration surface for OpenClaw, HermesAgent, and other attached agent runtimes is now the local
|
|
790
|
+
authenticated MCP endpoint:
|
|
791
|
+
|
|
792
|
+
- `POST <control_plane_endpoint>/mcp`
|
|
793
|
+
|
|
794
|
+
The MCP `tools/list` response uses the same endpoint keys as `.agent-participation/manifest.json`
|
|
795
|
+
(`list_missions`, `publish_mission`, `list_agent_payments`, `invoke_servicenet_agent`, and so on)
|
|
796
|
+
so operators can compare the generated manifest and the live MCP tool catalog directly. MCP
|
|
797
|
+
`tools/call` dispatches through the existing local control-plane routes, preserving bearer-token
|
|
798
|
+
auth, rate limiting, audit logging, signed event writes, and persistence behavior.
|
|
799
|
+
|
|
800
|
+
For agent runtimes that support stdio MCP servers, prefer the local proxy command instead of
|
|
801
|
+
configuring bearer-token headers by hand. The proxy reads `control.token` itself and
|
|
802
|
+
forwards MCP JSON-RPC requests to the local control plane:
|
|
803
|
+
|
|
804
|
+
```json
|
|
805
|
+
{
|
|
806
|
+
"mcpServers": {
|
|
807
|
+
"wattetheria": {
|
|
808
|
+
"command": "npx",
|
|
809
|
+
"args": [
|
|
810
|
+
"wattetheria",
|
|
811
|
+
"mcp-proxy"
|
|
812
|
+
]
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
```
|
|
817
|
+
|
|
818
|
+
If the runtime is attached to a source checkout instead of the default release deployment, pass the
|
|
819
|
+
node data directory explicitly:
|
|
820
|
+
|
|
821
|
+
```json
|
|
822
|
+
{
|
|
823
|
+
"mcpServers": {
|
|
824
|
+
"wattetheria": {
|
|
825
|
+
"command": "npx",
|
|
826
|
+
"args": [
|
|
827
|
+
"wattetheria",
|
|
828
|
+
"mcp-proxy",
|
|
829
|
+
"--data-dir",
|
|
830
|
+
"/Users/sac/Desktop/Watt/wattetheria/.wattetheria"
|
|
831
|
+
]
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
```
|
|
692
836
|
|
|
693
837
|
In Docker release deployments, those files live under the host bind mount, so a local AI assistant can read them directly from:
|
|
694
838
|
|
|
@@ -35,11 +35,14 @@ services:
|
|
|
35
35
|
WATTETHERIA_BRAIN_MODEL: ${WATTETHERIA_BRAIN_MODEL:-}
|
|
36
36
|
WATTETHERIA_BRAIN_API_KEY_ENV: ${WATTETHERIA_BRAIN_API_KEY_ENV:-}
|
|
37
37
|
WATTETHERIA_SERVICENET_BASE_URL: ${WATTETHERIA_SERVICENET_BASE_URL:-}
|
|
38
|
+
WATTETHERIA_GATEWAY_URLS: ${WATTETHERIA_GATEWAY_URLS:-}
|
|
39
|
+
WATTETHERIA_GATEWAY_CONFIG_PATH: ${WATTETHERIA_GATEWAY_CONFIG_PATH:-/var/lib/wattswarm/startup_config.json}
|
|
38
40
|
WATTETHERIA_AUTONOMY_ENABLED: ${WATTETHERIA_AUTONOMY_ENABLED:-false}
|
|
39
41
|
WATTETHERIA_AUTONOMY_INTERVAL_SEC: ${WATTETHERIA_AUTONOMY_INTERVAL_SEC:-30}
|
|
40
42
|
OPENCLAW_API_KEY: ${OPENCLAW_API_KEY:-}
|
|
41
43
|
volumes:
|
|
42
44
|
- ${WATTETHERIA_HOST_STATE_DIR:-./data/wattetheria}:/var/lib/wattetheria
|
|
45
|
+
- ${WATTSWARM_HOST_STATE_DIR:-./data/wattswarm}:/var/lib/wattswarm:ro
|
|
43
46
|
ports:
|
|
44
47
|
- "${WATTETHERIA_CONTROL_PLANE_BIND_HOST:-127.0.0.1}:${WATTETHERIA_CONTROL_PLANE_PORT:-7777}:7777"
|
|
45
48
|
extra_hosts:
|
package/lib/cli.js
CHANGED
|
@@ -4,6 +4,7 @@ const os = require("node:os");
|
|
|
4
4
|
const path = require("node:path");
|
|
5
5
|
const { spawnSync } = require("node:child_process");
|
|
6
6
|
const { createInterface } = require("node:readline/promises");
|
|
7
|
+
const readline = require("node:readline");
|
|
7
8
|
|
|
8
9
|
const PACKAGE_ROOT = path.resolve(__dirname, "..");
|
|
9
10
|
const PACKAGE_JSON = require(path.join(PACKAGE_ROOT, "package.json"));
|
|
@@ -91,9 +92,11 @@ Commands:
|
|
|
91
92
|
start Start an existing deployment
|
|
92
93
|
status Show docker compose status
|
|
93
94
|
update Resolve latest published release, pull, and restart
|
|
95
|
+
restart Recreate and restart the deployment
|
|
94
96
|
stop Stop the deployment
|
|
95
97
|
uninstall Stop the deployment and optionally remove volumes
|
|
96
98
|
logs Show docker compose logs
|
|
99
|
+
mcp-proxy Run stdio MCP proxy for the local Wattetheria node
|
|
97
100
|
doctor Check local prerequisites
|
|
98
101
|
help Show this help
|
|
99
102
|
|
|
@@ -108,6 +111,8 @@ Options:
|
|
|
108
111
|
--no-health-checks Skip HTTP health checks
|
|
109
112
|
--volumes With uninstall, remove named docker volumes
|
|
110
113
|
--purge With uninstall, remove the deployment directory
|
|
114
|
+
--data-dir <path> With mcp-proxy, override Wattetheria host state directory
|
|
115
|
+
--control-plane <url> With mcp-proxy, override local control-plane endpoint
|
|
111
116
|
`);
|
|
112
117
|
}
|
|
113
118
|
|
|
@@ -130,6 +135,8 @@ function parseArgs(argv) {
|
|
|
130
135
|
healthChecks: true,
|
|
131
136
|
volumes: false,
|
|
132
137
|
purge: false,
|
|
138
|
+
dataDir: null,
|
|
139
|
+
controlPlane: null,
|
|
133
140
|
composeArgs: [],
|
|
134
141
|
versionTarget: "release",
|
|
135
142
|
includeImages: false
|
|
@@ -155,6 +162,10 @@ function parseArgs(argv) {
|
|
|
155
162
|
options.volumes = true;
|
|
156
163
|
} else if (arg === "--purge") {
|
|
157
164
|
options.purge = true;
|
|
165
|
+
} else if (arg === "--data-dir") {
|
|
166
|
+
options.dataDir = requireValue(arg, argv[++index]);
|
|
167
|
+
} else if (arg === "--control-plane") {
|
|
168
|
+
options.controlPlane = requireValue(arg, argv[++index]);
|
|
158
169
|
} else if (arg === "--") {
|
|
159
170
|
options.composeArgs = argv.slice(index + 1);
|
|
160
171
|
break;
|
|
@@ -904,6 +915,31 @@ function getEnvValue(envMap, key, defaultValue) {
|
|
|
904
915
|
return value && value.trim() ? value : defaultValue;
|
|
905
916
|
}
|
|
906
917
|
|
|
918
|
+
function resolveConfiguredPath(baseDir, configured) {
|
|
919
|
+
if (!configured || !configured.trim()) {
|
|
920
|
+
return "";
|
|
921
|
+
}
|
|
922
|
+
return path.isAbsolute(configured) ? configured : path.resolve(baseDir, configured);
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
function resolveMcpProxyConfig(options) {
|
|
926
|
+
const envPath = envFilePath(options);
|
|
927
|
+
const envMap = fs.existsSync(envPath) ? readEnvFile(envPath) : defaultEnvMap();
|
|
928
|
+
const dataDir = options.dataDir
|
|
929
|
+
? path.resolve(options.dataDir)
|
|
930
|
+
: resolveConfiguredPath(
|
|
931
|
+
options.dir,
|
|
932
|
+
getEnvValue(envMap, "WATTETHERIA_HOST_STATE_DIR", "./data/wattetheria")
|
|
933
|
+
);
|
|
934
|
+
const tokenPath = path.join(dataDir, "control.token");
|
|
935
|
+
const host = getEnvValue(envMap, "WATTETHERIA_CONTROL_PLANE_BIND_HOST", "127.0.0.1");
|
|
936
|
+
const port = getEnvValue(envMap, "WATTETHERIA_CONTROL_PLANE_PORT", "7777");
|
|
937
|
+
return {
|
|
938
|
+
endpoint: (options.controlPlane || `http://${host}:${port}`).replace(/\/+$/, ""),
|
|
939
|
+
tokenPath
|
|
940
|
+
};
|
|
941
|
+
}
|
|
942
|
+
|
|
907
943
|
async function runHealthChecks(options) {
|
|
908
944
|
const envMap = readEnvFile(envFilePath(options));
|
|
909
945
|
const kernelHost = getEnvValue(envMap, "WATTETHERIA_CONTROL_PLANE_BIND_HOST", "127.0.0.1");
|
|
@@ -966,6 +1002,21 @@ async function start(options) {
|
|
|
966
1002
|
printSummary(options);
|
|
967
1003
|
}
|
|
968
1004
|
|
|
1005
|
+
async function restart(options) {
|
|
1006
|
+
await ensureDockerAvailable();
|
|
1007
|
+
if (!fs.existsSync(composeFilePath(options)) || !fs.existsSync(envFilePath(options))) {
|
|
1008
|
+
throw new Error("Deployment is not initialized. Run install first.");
|
|
1009
|
+
}
|
|
1010
|
+
console.log("Stopping release stack...");
|
|
1011
|
+
runCompose(options, ["down"]);
|
|
1012
|
+
console.log("Starting release stack...");
|
|
1013
|
+
runCompose(options, ["up", "-d"]);
|
|
1014
|
+
if (options.healthChecks) {
|
|
1015
|
+
await runHealthChecks(options);
|
|
1016
|
+
}
|
|
1017
|
+
printSummary(options);
|
|
1018
|
+
}
|
|
1019
|
+
|
|
969
1020
|
async function status(options) {
|
|
970
1021
|
await ensureDockerAvailable();
|
|
971
1022
|
runCompose(options, ["ps"]);
|
|
@@ -1015,6 +1066,94 @@ async function logs(options) {
|
|
|
1015
1066
|
runCompose(options, ["logs", ...options.composeArgs]);
|
|
1016
1067
|
}
|
|
1017
1068
|
|
|
1069
|
+
async function mcpProxy(options) {
|
|
1070
|
+
const { endpoint, tokenPath } = resolveMcpProxyConfig(options);
|
|
1071
|
+
if (!fs.existsSync(tokenPath)) {
|
|
1072
|
+
throw new Error(
|
|
1073
|
+
[
|
|
1074
|
+
`Wattetheria control token not found: ${tokenPath}`,
|
|
1075
|
+
"Start or initialize the local node first, or pass --data-dir <path>."
|
|
1076
|
+
].join("\n")
|
|
1077
|
+
);
|
|
1078
|
+
}
|
|
1079
|
+
const token = fs.readFileSync(tokenPath, "utf8").trim();
|
|
1080
|
+
if (!token) {
|
|
1081
|
+
throw new Error(`Wattetheria control token is empty: ${tokenPath}`);
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
const input = readline.createInterface({
|
|
1085
|
+
input: process.stdin,
|
|
1086
|
+
crlfDelay: Infinity,
|
|
1087
|
+
terminal: false
|
|
1088
|
+
});
|
|
1089
|
+
|
|
1090
|
+
for await (const line of input) {
|
|
1091
|
+
const trimmed = line.trim();
|
|
1092
|
+
if (!trimmed) {
|
|
1093
|
+
continue;
|
|
1094
|
+
}
|
|
1095
|
+
let request;
|
|
1096
|
+
try {
|
|
1097
|
+
request = JSON.parse(trimmed);
|
|
1098
|
+
} catch (error) {
|
|
1099
|
+
writeMcpResponse({
|
|
1100
|
+
jsonrpc: "2.0",
|
|
1101
|
+
id: null,
|
|
1102
|
+
error: {
|
|
1103
|
+
code: -32700,
|
|
1104
|
+
message: `parse error: ${error.message}`
|
|
1105
|
+
}
|
|
1106
|
+
});
|
|
1107
|
+
continue;
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
const hasId = Object.prototype.hasOwnProperty.call(request, "id");
|
|
1111
|
+
const response = await forwardMcpRequest(endpoint, token, request);
|
|
1112
|
+
if (hasId) {
|
|
1113
|
+
writeMcpResponse(response);
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
async function forwardMcpRequest(endpoint, token, request) {
|
|
1119
|
+
try {
|
|
1120
|
+
const response = await fetch(`${endpoint}/mcp`, {
|
|
1121
|
+
method: "POST",
|
|
1122
|
+
headers: {
|
|
1123
|
+
authorization: `Bearer ${token}`,
|
|
1124
|
+
"content-type": "application/json"
|
|
1125
|
+
},
|
|
1126
|
+
body: JSON.stringify(request)
|
|
1127
|
+
});
|
|
1128
|
+
const payload = await response.json().catch(() => null);
|
|
1129
|
+
if (response.ok) {
|
|
1130
|
+
return payload;
|
|
1131
|
+
}
|
|
1132
|
+
return {
|
|
1133
|
+
jsonrpc: "2.0",
|
|
1134
|
+
id: request.id ?? null,
|
|
1135
|
+
error: {
|
|
1136
|
+
code: -32000,
|
|
1137
|
+
message: `local Wattetheria MCP returned HTTP ${response.status}`,
|
|
1138
|
+
data: payload
|
|
1139
|
+
}
|
|
1140
|
+
};
|
|
1141
|
+
} catch (error) {
|
|
1142
|
+
return {
|
|
1143
|
+
jsonrpc: "2.0",
|
|
1144
|
+
id: request.id ?? null,
|
|
1145
|
+
error: {
|
|
1146
|
+
code: -32000,
|
|
1147
|
+
message: error.message
|
|
1148
|
+
}
|
|
1149
|
+
};
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
function writeMcpResponse(response) {
|
|
1154
|
+
process.stdout.write(`${JSON.stringify(response)}\n`);
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1018
1157
|
function doctor() {
|
|
1019
1158
|
const status = getDockerStatus();
|
|
1020
1159
|
if (!status.ready) {
|
|
@@ -1052,7 +1191,7 @@ function printImages(options) {
|
|
|
1052
1191
|
}
|
|
1053
1192
|
|
|
1054
1193
|
function shouldPrintBanner(command) {
|
|
1055
|
-
return !["help", "--help", "-h", "version", "images"].includes(command);
|
|
1194
|
+
return !["help", "--help", "-h", "version", "images", "mcp-proxy"].includes(command);
|
|
1056
1195
|
}
|
|
1057
1196
|
|
|
1058
1197
|
function printBanner(options) {
|
|
@@ -1087,6 +1226,9 @@ async function run(argv) {
|
|
|
1087
1226
|
case "update":
|
|
1088
1227
|
await update(options);
|
|
1089
1228
|
return;
|
|
1229
|
+
case "restart":
|
|
1230
|
+
await restart(options);
|
|
1231
|
+
return;
|
|
1090
1232
|
case "stop":
|
|
1091
1233
|
case "down":
|
|
1092
1234
|
await stop(options);
|
|
@@ -1097,6 +1239,9 @@ async function run(argv) {
|
|
|
1097
1239
|
case "logs":
|
|
1098
1240
|
await logs(options);
|
|
1099
1241
|
return;
|
|
1242
|
+
case "mcp-proxy":
|
|
1243
|
+
await mcpProxy(options);
|
|
1244
|
+
return;
|
|
1100
1245
|
case "doctor":
|
|
1101
1246
|
doctor();
|
|
1102
1247
|
return;
|