-
Notifications
You must be signed in to change notification settings - Fork 114
Expand file tree
/
Copy pathbase.py
More file actions
125 lines (102 loc) · 4.48 KB
/
base.py
File metadata and controls
125 lines (102 loc) · 4.48 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
"""Base adapter interface for MCP clients."""
import re
from abc import ABC, abstractmethod
_INPUT_VAR_RE = re.compile(r"\$\{input:([^}]+)\}")
class MCPClientAdapter(ABC):
"""Base adapter for MCP clients."""
# Whether this adapter's config path is user/global-scoped (e.g.
# ``~/.copilot/``) rather than workspace-scoped (e.g. ``.vscode/``).
# Adapters that target a global path should override this to ``True``
# so that ``apm install --global`` can install MCP servers to them.
supports_user_scope: bool = False
@abstractmethod
def get_config_path(self):
"""Get the path to the MCP configuration file."""
pass
@abstractmethod
def update_config(self, config_updates):
"""Update the MCP configuration."""
pass
@abstractmethod
def get_current_config(self):
"""Get the current MCP configuration."""
pass
@abstractmethod
def configure_mcp_server(self, server_url, server_name=None, enabled=True, env_overrides=None, server_info_cache=None, runtime_vars=None):
"""Configure an MCP server in the client configuration.
Args:
server_url (str): URL of the MCP server.
server_name (str, optional): Name of the server. Defaults to None.
enabled (bool, optional): Whether to enable the server. Defaults to True.
env_overrides (dict, optional): Environment variable overrides. Defaults to None.
server_info_cache (dict, optional): Pre-fetched server info to avoid duplicate registry calls.
runtime_vars (dict, optional): Runtime variable values. Defaults to None.
Returns:
bool: True if successful, False otherwise.
"""
pass
@staticmethod
def _infer_registry_name(package):
"""Infer the registry type from package metadata.
The MCP registry API often returns empty ``registry_name``. This
method derives the registry from explicit fields first, then falls
back to heuristics on the package name.
Args:
package (dict): A single package entry from the registry.
Returns:
str: Inferred registry name (e.g. "npm", "pypi", "docker") or "".
"""
if not package:
return ""
explicit = package.get("registry_name", "")
if explicit:
return explicit
name = package.get("name", "")
runtime_hint = package.get("runtime_hint", "")
# Infer from runtime_hint
if runtime_hint in ("npx", "npm"):
return "npm"
if runtime_hint in ("uvx", "pip", "pipx"):
return "pypi"
if runtime_hint == "docker":
return "docker"
if runtime_hint in ("dotnet", "dnx"):
return "nuget"
# Infer from package name patterns
if name.startswith("@") and "/" in name:
return "npm" # scoped npm package, e.g. @azure/mcp
if name.startswith(("ghcr.io/", "mcr.microsoft.com/", "docker.io/")):
return "docker"
if name.startswith("https://") and name.endswith(".mcpb"):
return "mcpb"
# PascalCase with dots usually means nuget (e.g. Azure.Mcp)
if "." in name and not name.startswith("http") and name[0].isupper():
return "nuget"
return ""
@staticmethod
def _warn_input_variables(mapping, server_name, runtime_label):
"""Emit a warning for each ``${input:...}`` reference found in *mapping*.
Runtimes that do not support VS Code-style input prompts (Copilot CLI,
Codex CLI, etc.) should call this so users know their placeholders
will not be resolved at runtime.
Args:
mapping (dict): Header or env dict to scan.
server_name (str): Server name for the warning message.
runtime_label (str): Human-readable runtime name (e.g. "Copilot CLI").
"""
if not mapping:
return
seen: set = set()
for value in mapping.values():
if not isinstance(value, str):
continue
for match in _INPUT_VAR_RE.finditer(value):
var_id = match.group(1)
if var_id in seen:
continue
seen.add(var_id)
print(
f"[!] Warning: ${{input:{var_id}}} in server "
f"'{server_name}' will not be resolved \u2014 "
f"{runtime_label} does not support input variable prompts"
)