更新godot ai 到最新版本
This commit is contained in:
@@ -89,16 +89,55 @@ var path_template: Dictionary = {}
|
||||
## `path_template` remains the fallback.
|
||||
var config_path_candidates: Dictionary = {}
|
||||
|
||||
## Optional global JSON config files merged by the client from lowest to highest
|
||||
## precedence. Unlike `config_path_candidates`, every existing file participates;
|
||||
## Configure updates the effective last definition, status verifies it, and
|
||||
## Remove clears every global definition transactionally.
|
||||
## Optional global JSON config files merged by the client. Unlike
|
||||
## `config_path_candidates`, every existing file participates. By default the
|
||||
## client keeps the LAST definition: Configure updates the effective last
|
||||
## definition, status verifies it, and Remove clears every global definition
|
||||
## transactionally. Clients that keep the FIRST definition instead set
|
||||
## `config_merge_first_wins` and declare tiers in the client's read order;
|
||||
## the folds then target the first tier defining the server, so a write
|
||||
## updates the file that is already effective and a fresh entry lands on
|
||||
## tiers[0] — the client's own primary file.
|
||||
var config_merge_path_templates: Dictionary = {}
|
||||
## Project-relative tiers that may override the global files. Their root is the
|
||||
## external client's working directory, which the Godot process cannot know.
|
||||
## The strategy checks plausible roots and fails closed instead of mutating them.
|
||||
var config_merge_project_paths: PackedStringArray = PackedStringArray()
|
||||
|
||||
## True when the client keeps the FIRST definition of a duplicated server and
|
||||
## ignores later ones (omp-native: project `.omp/mcp.json` precedes
|
||||
## `.omp/.mcp.json`, then the user scope's `mcp.json` precedes `.mcp.json`).
|
||||
## Flips every merge-tier fold — Configure target, status verification, and
|
||||
## the manual/Open-Reveal target resolution — from last-definition-wins to
|
||||
## first. With tiers declared in read order, Configure updates the tier that
|
||||
## already owns the server instead of creating a higher-priority entry that
|
||||
## shadows a lower tier's user state (#1085).
|
||||
var config_merge_first_wins: bool = false
|
||||
|
||||
## Directory-glob templates whose matches make the user-scope config
|
||||
## destination unknowable from the editor (omp: `~/.omp/profiles/*`). One `*`
|
||||
## per directory segment, expanded by `McpPathTemplate.expand_path_candidates`.
|
||||
## Any existing match means a named profile may own the user scope: the active
|
||||
## profile is chosen per client launch and never persisted where the editor can
|
||||
## read it, so Configure and Remove fail closed with the matched paths and
|
||||
## status reports the ambiguity instead of green-lighting the default file.
|
||||
var config_scope_globs: PackedStringArray = PackedStringArray()
|
||||
|
||||
## Environment names that make the effective config destination unknown.
|
||||
## Configure, Remove and status refuse while any is set; values are not reported.
|
||||
var config_scope_envs: PackedStringArray = PackedStringArray()
|
||||
|
||||
## Top-level server-name denylist in the first declared merge tier (the default
|
||||
## primary file), independent of which tier owns the entry. Configure and status
|
||||
## refuse a suppressed name; Remove preserves this user state. Empty disables it.
|
||||
var config_denylist_key: String = ""
|
||||
|
||||
## Effective-entry enable toggle and primary force-enable list. Boolean false
|
||||
## and strings "false"/"0" suppress the entry unless the allowlist names it.
|
||||
## The denylist still wins. Empty keys disable these checks.
|
||||
var config_enabled_key: String = ""
|
||||
var config_allowlist_key: String = ""
|
||||
|
||||
## De-duplicate persistent path-ambiguity warnings across recurring status
|
||||
## refreshes. The actionable message still returns on every resolution; only
|
||||
## the editor-console echo is single-shot until the ambiguity clears/changes.
|
||||
|
||||
@@ -62,6 +62,12 @@ static func _configure_merged(
|
||||
var launch_error := command_launch_error(client, launch)
|
||||
if not launch_error.is_empty():
|
||||
return {"status": "error", "message": launch_error}
|
||||
## A matched `config_scope_globs` directory (omp named profiles) means the
|
||||
## user scope may live elsewhere for the running client; writing the
|
||||
## default tiers would false-succeed (#1085).
|
||||
var scope_error := _scope_ambiguity_error(client)
|
||||
if not scope_error.is_empty():
|
||||
return {"status": "error", "message": scope_error}
|
||||
var project := _load_project_definitions(client, server_name, project_roots)
|
||||
if not project.get("ok", false):
|
||||
return {"status": "error", "message": str(project.get("error", "Cannot inspect project config tiers"))}
|
||||
@@ -74,16 +80,26 @@ static func _configure_merged(
|
||||
var tiers: Array = loaded.get("tiers", [])
|
||||
if tiers.is_empty():
|
||||
return {"status": "error", "message": "Could not resolve config path for %s on this OS" % client.display_name}
|
||||
var denylist_error := _denylist_error(client, tiers, server_name)
|
||||
if not denylist_error.is_empty():
|
||||
return {"status": "error", "message": denylist_error}
|
||||
var target_index := 0
|
||||
for index in range(tiers.size()):
|
||||
var config: Dictionary = tiers[index]["data"]
|
||||
var holder := _walk_path(config, select_server_key_path(config, client))
|
||||
if holder is Dictionary and holder.has(server_name):
|
||||
target_index = index
|
||||
## First-definition-wins clients keep the earliest defining tier;
|
||||
## later tiers are dead, so updating one of those changes nothing.
|
||||
if _first_wins(client):
|
||||
break
|
||||
var tier: Dictionary = tiers[target_index]
|
||||
var config: Dictionary = tier["data"]
|
||||
var holder := _ensure_path(config, select_server_key_path(config, client))
|
||||
var existing: Variant = holder.get(server_name, null)
|
||||
var disabled_error := _disabled_entry_error(client, tiers, server_name, existing, str(tier["path"]))
|
||||
if not disabled_error.is_empty():
|
||||
return {"status": "error", "message": disabled_error}
|
||||
holder[server_name] = build_entry(client, server_url, existing, launch)
|
||||
var path := str(tier["path"])
|
||||
# F5: refuse to re-serialize a tier whose parsed integers above 2^53 would
|
||||
@@ -148,7 +164,7 @@ static func check_status_details(
|
||||
return _entry_status_details(client, holder[server_name], server_url, launch)
|
||||
|
||||
|
||||
## Verify the effective last definition after applying the client's merge order.
|
||||
## Verify the effective definition after applying the client's merge order.
|
||||
static func _check_status_merged(
|
||||
client: McpClient,
|
||||
server_name: String,
|
||||
@@ -156,39 +172,60 @@ static func _check_status_merged(
|
||||
launch: Dictionary,
|
||||
project_roots: PackedStringArray,
|
||||
) -> Dictionary:
|
||||
var scope_error := _scope_ambiguity_error(client)
|
||||
if not scope_error.is_empty():
|
||||
return {"status": McpClient.Status.ERROR, "error_msg": scope_error}
|
||||
var allow_comments := _status_allows_comments(client)
|
||||
var loaded := _load_merge_tiers(client, allow_comments)
|
||||
if not loaded.get("ok", false):
|
||||
return {"status": McpClient.Status.ERROR, "error_msg": str(loaded.get("error", "Cannot read merged config tiers"))}
|
||||
var effective: Variant = null
|
||||
var effective_path := ""
|
||||
for tier in loaded.get("tiers", []):
|
||||
var config: Dictionary = tier["data"]
|
||||
var holder := _walk_path(config, select_server_key_path(config, client))
|
||||
if holder is Dictionary and holder.has(server_name):
|
||||
effective = holder[server_name]
|
||||
effective_path = str(tier["path"])
|
||||
## First-definition-wins: the earliest defining tier is the one the
|
||||
## client actually reads; stop instead of overwriting with a dead one.
|
||||
if _first_wins(client):
|
||||
break
|
||||
var project := _load_project_definitions(client, server_name, project_roots, allow_comments)
|
||||
if not project.get("ok", false):
|
||||
return {"status": McpClient.Status.ERROR, "error_msg": str(project.get("error", "Cannot inspect project config tiers"))}
|
||||
var project_tiers: Array = project.get("tiers", [])
|
||||
if effective != null or not project_tiers.is_empty():
|
||||
var denylist_error := _denylist_error(client, loaded.get("tiers", []), server_name)
|
||||
if not denylist_error.is_empty():
|
||||
return {"status": McpClient.Status.CONFIGURED_MISMATCH, "error_msg": denylist_error}
|
||||
if not project_tiers.is_empty():
|
||||
# Last-definition-wins mirrors the global-tier fold above and how
|
||||
# pi-codemode-mcp merges project tiers on disk. Earlier tiers are dead
|
||||
# once a later one defines the same server, so an early-stale entry
|
||||
# doesn't make Pi's effective config drift (codex-review finding F2).
|
||||
# Pass `[latest]` to `_project_override_message` so the error names only
|
||||
# the file the user actually has to edit.
|
||||
var latest: Dictionary = project_tiers[project_tiers.size() - 1]
|
||||
var details := _entry_status_details(client, latest["entry"], server_url, launch)
|
||||
# The project tier that drives the client's effective config: the
|
||||
# latest for last-wins clients (pi-codemode-mcp merges project tiers
|
||||
# that way; earlier tiers are dead once a later one defines the same
|
||||
# server — codex-review finding F2), the earliest for first-wins
|
||||
# clients (omp reads `.omp/mcp.json` before `.omp/.mcp.json`). Pass
|
||||
# `[effective_tier]` to `_project_override_message` so the error names
|
||||
# only the file the user actually has to edit.
|
||||
var effective_index := 0 if _first_wins(client) else project_tiers.size() - 1
|
||||
var effective_tier: Dictionary = project_tiers[effective_index]
|
||||
var disabled_error := _disabled_entry_error(client, loaded.get("tiers", []), server_name, effective_tier["entry"], str(effective_tier["path"]))
|
||||
if not disabled_error.is_empty():
|
||||
return {"status": McpClient.Status.CONFIGURED_MISMATCH, "error_msg": disabled_error}
|
||||
var details := _entry_status_details(client, effective_tier["entry"], server_url, launch)
|
||||
if details.get("status") != McpClient.Status.CONFIGURED:
|
||||
## Keep `owned` from the effective entry: the post-update migration
|
||||
## decides from it whether this mismatch is ours to repin.
|
||||
var mismatch := details.duplicate()
|
||||
mismatch["status"] = McpClient.Status.CONFIGURED_MISMATCH
|
||||
mismatch["error_msg"] = _project_override_message([latest], "update or remove", client.display_name, server_name)
|
||||
mismatch["error_msg"] = _project_override_message([effective_tier], "update or remove", client.display_name, server_name)
|
||||
return mismatch
|
||||
return {"status": McpClient.Status.CONFIGURED, "error_msg": ""}
|
||||
if effective == null:
|
||||
return {"status": McpClient.Status.NOT_CONFIGURED, "error_msg": ""}
|
||||
var disabled_error := _disabled_entry_error(client, loaded.get("tiers", []), server_name, effective, effective_path)
|
||||
if not disabled_error.is_empty():
|
||||
return {"status": McpClient.Status.CONFIGURED_MISMATCH, "error_msg": disabled_error}
|
||||
return _entry_status_details(client, effective, server_url, launch)
|
||||
|
||||
|
||||
@@ -247,6 +284,12 @@ static func remove(
|
||||
static func _remove_merged(
|
||||
client: McpClient, server_name: String, project_roots: PackedStringArray
|
||||
) -> Dictionary:
|
||||
## Scope ambiguity (omp named profiles) blocks Remove too: clearing the
|
||||
## default files while a profile reads its own would report a success the
|
||||
## running client never observes (#1085).
|
||||
var scope_error := _scope_ambiguity_error(client)
|
||||
if not scope_error.is_empty():
|
||||
return {"status": "error", "message": scope_error}
|
||||
var project := _load_project_definitions(client, server_name, project_roots)
|
||||
if not project.get("ok", false):
|
||||
return {"status": "error", "message": str(project.get("error", "Cannot inspect project config tiers"))}
|
||||
@@ -633,6 +676,63 @@ static func _uses_merge_tiers(client: McpClient) -> bool:
|
||||
var templates = client.get("config_merge_path_templates")
|
||||
return templates is Dictionary and not templates.is_empty()
|
||||
|
||||
## True when the client resolves duplicate definitions first-wins (see
|
||||
## `McpClient.config_merge_first_wins`). Dynamic get keeps a mixed-snapshot
|
||||
## self-update parse-safe against an older McpClient base (#398/#736).
|
||||
static func _first_wins(client: McpClient) -> bool:
|
||||
return bool(client.get("config_merge_first_wins"))
|
||||
|
||||
|
||||
## Fail-closed gate for clients whose user scope can be relocated by something
|
||||
## the editor cannot observe (omp named profiles). Any directory matching a
|
||||
## declared `config_scope_globs` template means the effective destination is
|
||||
## ambiguous. Declared relocation environment names also cause refusal.
|
||||
static func _scope_ambiguity_error(client: McpClient) -> String:
|
||||
var env_names: Variant = client.get("config_scope_envs")
|
||||
if env_names is PackedStringArray:
|
||||
for env_name in env_names:
|
||||
if not McpPathTemplate.env_lookup(env_name).is_empty():
|
||||
return "%s is set and may relocate %s configuration. Confirm the active client config path and edit the entry manually; default-profile files were not changed." % [env_name, client.display_name]
|
||||
var globs: Variant = client.get("config_scope_globs")
|
||||
if not (globs is PackedStringArray):
|
||||
return ""
|
||||
var found := PackedStringArray()
|
||||
for template in globs:
|
||||
for candidate in McpPathTemplate.expand_path_candidates(String(template)):
|
||||
if DirAccess.dir_exists_absolute(candidate) and not found.has(candidate):
|
||||
found.append(candidate)
|
||||
if found.is_empty():
|
||||
return ""
|
||||
return (
|
||||
"%s named profiles found at %s. A named profile reads only its own agent config and the active profile is chosen per %s launch, so this editor cannot resolve the effective destination; edit the entry manually."
|
||||
% [client.display_name, ", ".join(found), client.display_name]
|
||||
)
|
||||
|
||||
|
||||
static func _disabled_entry_error(client: McpClient, tiers: Array, server_name: String, entry: Variant, path: String) -> String:
|
||||
if client.config_enabled_key.is_empty() or not (entry is Dictionary):
|
||||
return ""
|
||||
var enabled: Variant = entry.get(client.config_enabled_key)
|
||||
var disabled: bool = (enabled is bool and not enabled) or (enabled is String and enabled.to_lower() in ["false", "0"])
|
||||
if not disabled:
|
||||
return ""
|
||||
if not client.config_allowlist_key.is_empty() and not tiers.is_empty():
|
||||
var allowed: Variant = tiers[0]["data"].get(client.config_allowlist_key)
|
||||
if allowed is Array and allowed.has(server_name):
|
||||
return ""
|
||||
return "%s is disabled by %s in %s. Re-enable it in %s before configuring; all files were left unchanged." % [server_name, client.config_enabled_key, path, client.display_name]
|
||||
|
||||
|
||||
static func _denylist_error(client: McpClient, tiers: Array, server_name: String) -> String:
|
||||
var key: Variant = client.get("config_denylist_key")
|
||||
if not (key is String) or String(key).is_empty() or tiers.is_empty():
|
||||
return ""
|
||||
var primary: Dictionary = tiers[0]
|
||||
var denylist: Variant = primary["data"].get(key, null)
|
||||
if denylist is Array and denylist.has(server_name):
|
||||
return "%s is disabled by %s in %s. Review that primary config manually before configuring; all files were left unchanged." % [server_name, key, primary["path"]]
|
||||
return ""
|
||||
|
||||
|
||||
## Resolve the global config tiers in the client's documented merge order.
|
||||
static func _merge_paths(client: McpClient) -> PackedStringArray:
|
||||
@@ -765,11 +865,12 @@ static func _write_transaction(writes: Array[Dictionary]) -> Dictionary:
|
||||
## land on the file that actually drives Pi's effective config.
|
||||
##
|
||||
## Resolution order (codex round 3, F-3-4):
|
||||
## 1. Latest project tier containing the entry — F2 last-wins means the
|
||||
## latest of `.pi/mcp.json` and `.mcp.json` wins (matches `_check_status_merged`).
|
||||
## 2. Latest global tier containing the entry — already iterated in
|
||||
## merge order, last-iterated-wins (same loop pattern as
|
||||
## `manual_target_details` lines 660-665).
|
||||
## 1. The project tier that wins the client's fold — the latest of
|
||||
## `.pi/mcp.json` and `.mcp.json` for last-wins clients, the earliest
|
||||
## for first-wins clients (matches `_check_status_merged`).
|
||||
## 2. The global tier that wins the client's fold — for last-wins clients
|
||||
## the last-iterated match, for first-wins clients the first match
|
||||
## (tiers arrive in the client's read order).
|
||||
## 3. "" when no tier has the entry — caller decides what fallback
|
||||
## (`path_template`) to use.
|
||||
##
|
||||
@@ -789,24 +890,28 @@ static func authoritative_tier_path(
|
||||
return ""
|
||||
var project_tiers: Array = project.get("tiers", [])
|
||||
if not project_tiers.is_empty():
|
||||
# F2 last-wins: latest project tier is authoritative. When there are
|
||||
# multiple project tiers, the user-visible status is driven by the
|
||||
# latest, so the Open/Reveal buttons should send them there too.
|
||||
var latest: Dictionary = project_tiers[project_tiers.size() - 1]
|
||||
return str(latest.get("path", ""))
|
||||
# The project tier that drives the user-visible status (F2: latest for
|
||||
# last-wins clients, earliest for first-wins ones), so the Open/Reveal
|
||||
# buttons send the user to the file they actually have to edit.
|
||||
var effective_index := 0 if _first_wins(client) else project_tiers.size() - 1
|
||||
var effective_tier: Dictionary = project_tiers[effective_index]
|
||||
return str(effective_tier.get("path", ""))
|
||||
var loaded := _load_merge_tiers(client, allow_comments)
|
||||
if not bool(loaded.get("ok", false)):
|
||||
return ""
|
||||
var tiers: Array = loaded.get("tiers", [])
|
||||
# Iterate in priority order, overwrite `selected_path` each time we
|
||||
# find a tier containing the entry. With Pi's merge path order
|
||||
# [mcp.json, .mcp.json] this leaves the higher-precedence `.mcp.json`.
|
||||
# Iterate in the client's read order. Last-wins clients overwrite
|
||||
# `selected_path` on every match (Pi's [mcp.json, .mcp.json] order then
|
||||
# leaves the higher-precedence `.mcp.json`); first-wins clients stop at
|
||||
# the first match, which is the file the client actually reads.
|
||||
var selected_path: String = ""
|
||||
for tier in tiers:
|
||||
var data: Dictionary = tier.get("data", {})
|
||||
var holder: Variant = _walk_path(data, select_server_key_path(data, client))
|
||||
if holder is Dictionary and holder.has(server_name):
|
||||
selected_path = str(tier.get("path", ""))
|
||||
if _first_wins(client):
|
||||
break
|
||||
return selected_path
|
||||
|
||||
|
||||
@@ -845,6 +950,10 @@ static func manual_target_details(
|
||||
var holder := _walk_path(config, select_server_key_path(config, client))
|
||||
if holder is Dictionary and holder.has(server_name):
|
||||
selected = tier
|
||||
## First-wins clients read the earliest defining tier; later
|
||||
## matches are dead and must not redirect the manual flow.
|
||||
if _first_wins(client):
|
||||
break
|
||||
if selected != null:
|
||||
var config: Dictionary = selected["data"]
|
||||
return {
|
||||
|
||||
@@ -199,7 +199,10 @@ static func _build_json(
|
||||
target = {"path": resolved_path, "key_path": client.server_key_path}
|
||||
var target_path := str(target.get("path", resolved_path))
|
||||
var key_path: PackedStringArray = target.get("key_path", client.server_key_path)
|
||||
var key := key_path[0] if key_path.size() > 0 else "mcpServers"
|
||||
## Name the whole server-map path. A nested map (ZCode's `mcp.servers`) must
|
||||
## not collapse to its first segment, or the instructions place the entry a
|
||||
## level too high for the client parser to find.
|
||||
var key := ".".join(key_path) if key_path.size() > 0 else "mcpServers"
|
||||
if client.command_shape != McpClient.CommandShape.NONE:
|
||||
var lines: Array[String] = []
|
||||
var launch_error := McpJsonStrategy.command_launch_error(client, launch)
|
||||
|
||||
@@ -37,6 +37,8 @@ const _CLIENT_SCRIPT_PATHS := [
|
||||
"res://addons/godot_ai/clients/hermes.gd",
|
||||
"res://addons/godot_ai/clients/pi.gd",
|
||||
"res://addons/godot_ai/clients/deepseek_harness.gd",
|
||||
"res://addons/godot_ai/clients/zcode.gd",
|
||||
"res://addons/godot_ai/clients/omp.gd",
|
||||
]
|
||||
|
||||
static var _instances: Array[McpClient] = []
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
@tool
|
||||
extends McpClient
|
||||
|
||||
## Oh My Pi (omp): https://github.com/can1357/oh-my-pi
|
||||
## omp keeps the FIRST definition of a duplicated server: project
|
||||
## `.omp/mcp.json` shadows `.omp/.mcp.json`, then the active user scope's
|
||||
## `mcp.json` shadows its `.mcp.json`. The tiers below are declared in that
|
||||
## read order with `config_merge_first_wins`, so Configure updates the file
|
||||
## that already owns the server instead of shadowing a compatibility
|
||||
## entry's user state (#1085). omp never writes the compatibility files
|
||||
## itself; this adapter updates an existing compatibility entry in place.
|
||||
## A fresh entry is created only on tiers[0].
|
||||
##
|
||||
## The user scope can be relocated per launch (`omp --profile`,
|
||||
## OMP_PROFILE/PI_PROFILE). `config_scope_globs` fails Configure/Remove
|
||||
## closed while any named profile exists, because the active profile is
|
||||
## chosen at client launch and is not persisted anywhere the editor can
|
||||
## read — status reports the ambiguity instead of green-lighting the
|
||||
## default file.
|
||||
|
||||
|
||||
func _init() -> void:
|
||||
id = "omp"
|
||||
display_name = "Oh My Pi"
|
||||
config_type = "json"
|
||||
path_template = {
|
||||
"unix": "~/.omp/agent/mcp.json",
|
||||
"windows": "$USERPROFILE/.omp/agent/mcp.json",
|
||||
}
|
||||
## Declared in omp's read order (primary first, compatibility second);
|
||||
## `_first_wins` folds stop at the first tier defining the server, so a
|
||||
## write updates the effective file and a fresh entry lands on the
|
||||
## primary. Status still verifies against every existing tier.
|
||||
config_merge_path_templates = {
|
||||
"unix": PackedStringArray([
|
||||
"~/.omp/agent/mcp.json",
|
||||
"~/.omp/agent/.mcp.json",
|
||||
]),
|
||||
"windows": PackedStringArray([
|
||||
"$USERPROFILE/.omp/agent/mcp.json",
|
||||
"$USERPROFILE/.omp/agent/.mcp.json",
|
||||
]),
|
||||
}
|
||||
config_merge_project_paths = PackedStringArray([".omp/mcp.json", ".omp/.mcp.json"])
|
||||
config_merge_first_wins = true
|
||||
config_scope_globs = PackedStringArray(["~/.omp/profiles/*"])
|
||||
config_scope_envs = PackedStringArray(["PI_CODING_AGENT_DIR", "PI_CONFIG_DIR", "OMP_PROFILE", "PI_PROFILE"])
|
||||
## The primary user file owns this override even for a compatibility entry.
|
||||
config_denylist_key = "disabledServers"
|
||||
config_enabled_key = "enabled"
|
||||
config_allowlist_key = "enabledServers"
|
||||
server_key_path = PackedStringArray(["mcpServers"])
|
||||
command_shape = McpClient.CommandShape.FLAT
|
||||
command_legacy_keys = PackedStringArray(["url", "headers", "type"])
|
||||
## Seeded only on a fresh entry; reconfigure preserves the effective
|
||||
## entry's values. OMP_MCP_TIMEOUT_MS may override this per-server
|
||||
## timeout.
|
||||
command_initial_fields = {"enabled": true, "timeout": 300000}
|
||||
command_timeout_fields = PackedStringArray(["timeout"])
|
||||
command_user_fields = PackedStringArray(["enabled", "timeout", "env", "cwd"])
|
||||
## omp creates `~/.omp/agent` on first launch (agent.db and friends)
|
||||
## before any MCP server is configured, so the directory is the honest
|
||||
## install signal — the config leaf may not exist yet.
|
||||
detect_paths = PackedStringArray(["~/.omp/agent"])
|
||||
@@ -0,0 +1 @@
|
||||
uid://b1daig0jwxvfb
|
||||
@@ -0,0 +1,40 @@
|
||||
@tool
|
||||
extends McpClient
|
||||
|
||||
## ZCode (Z.AI's GLM coding agent): https://zcode.z.ai/en/docs/mcp-services
|
||||
## User-scope MCP servers live in `~/.zcode/cli/config.json` under the nested
|
||||
## `mcp.servers` map; the workspace scope is `<project>/.zcode/config.json`,
|
||||
## which this descriptor does not target because the client's working directory
|
||||
## is unknown to the editor. Entries are flat command/args/env, and
|
||||
## `type: "stdio"` is what the settings form, the bundled plugin manifest, and
|
||||
## working third-party integrations write - it also repins a stale
|
||||
## `type: "http"` left on a hand-added remote entry. `enable` (singular) is
|
||||
## ZCode's documented user-state key (absence means enabled), so it is
|
||||
## preserved rather than written.
|
||||
##
|
||||
## Manual-only: adding any `.zcode` server makes ZCode skip `~/.agents/mcp.json`
|
||||
## for that scope entirely (documented `.zcode`-first rule, no merging). An
|
||||
## automatic write could therefore silently disable every server a user keeps in
|
||||
## the fallback, so Configure and Remove only render the manual entry; moving
|
||||
## those entries into the native file stays the user's explicit choice.
|
||||
|
||||
|
||||
func _init() -> void:
|
||||
id = "zcode"
|
||||
display_name = "ZCode"
|
||||
config_type = "json"
|
||||
## A native write would shadow `~/.agents/mcp.json`; see the class comment.
|
||||
automatic_config_edits = false
|
||||
path_template = {
|
||||
"unix": "~/.zcode/cli/config.json",
|
||||
"windows": "$USERPROFILE/.zcode/cli/config.json",
|
||||
}
|
||||
server_key_path = PackedStringArray(["mcp", "servers"])
|
||||
command_shape = McpClient.CommandShape.FLAT
|
||||
command_transport_key = "type"
|
||||
command_transport_value = "stdio"
|
||||
command_legacy_keys = PackedStringArray(["url", "headers"])
|
||||
command_user_fields = PackedStringArray(["env", "enable"])
|
||||
## ZCode creates `~/.zcode` on first launch; the config leaf may not exist
|
||||
## yet, so the directory is the installed signal.
|
||||
detect_paths = PackedStringArray(["~/.zcode"])
|
||||
@@ -0,0 +1 @@
|
||||
uid://b0xpusa88vykn
|
||||
Reference in New Issue
Block a user