Skip to content
Open
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
54 changes: 26 additions & 28 deletions awscli/customizations/agenttoolkit/agents.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@

SKILL_FILENAME = 'SKILL.md'
SKILL_METADATA_FILENAME = '.aws-skill-metadata'
AWS_MCP_SERVER_KEY = 'aws-mcp'
AWS_KNOWLEDGE_MCP_SERVER_KEY = 'aws-knowledge-mcp-server'
AWS_KNOWLEDGE_MCP_SERVER_URL = 'https://knowledge-mcp.global.api.aws'
UNIVERSAL_ROW_ID = 'universal'


Expand All @@ -40,17 +41,8 @@ class McpConfigureAction(enum.Enum):
SKIPPED = 'skipped'


_AWS_MCP_PROXY_ARGS = [
'mcp-proxy-for-aws@latest',
'https://aws-mcp.us-east-1.api.aws/mcp',
'--metadata',
'INSTALL_SOURCE=aws-cli',
]


DEFAULT_MCP_SERVER_CONFIG = {
'command': 'uvx',
'args': _AWS_MCP_PROXY_ARGS,
'url': AWS_KNOWLEDGE_MCP_SERVER_URL,
}


Expand Down Expand Up @@ -98,20 +90,19 @@ class AgentConfig:
agents use ``mcpServers``; OpenCode uses ``mcp``."""

mcp_extra_config: Optional[dict] = None
"""Extra fields merged into the default MCP server entry (e.g.
Kiro requires ``timeout`` and ``transport``). Used only when
"""Extra fields merged into the default ``{url}`` MCP server entry
(e.g. Claude Code requires ``type``). Used only when
``mcp_server_entry`` is not set."""

mcp_server_entry: Optional[dict] = None
"""Complete MCP server entry, replacing the default schema. Use
when an agent expects a different shape than ``{command, args}``
(e.g. OpenCode expects ``{type, command: [...]}``)."""
when an agent expects a different remote-server shape (e.g. Gemini CLI
expects ``httpUrl`` and Windsurf documents ``serverUrl``)."""

mcp_shell_command: Optional[list] = None
"""Argv for a CLI invocation that registers the AWS MCP server
(e.g. ``codex mcp add ...``). Used for agents whose MCP config is
not JSON. The wizard skips MCP setup if the executable is not on
PATH."""
"""Argv for a CLI invocation that registers the AWS Knowledge MCP server
(e.g. ``codex mcp add ... --url ...``). Used for agents whose MCP config
is not JSON. The wizard skips MCP setup if the executable is not on PATH."""

def __post_init__(self):
if self.mcp_extra_config is None:
Expand Down Expand Up @@ -225,7 +216,7 @@ def configure_mcp_server(self):
existing_config = self._read_mcp_config(config_path)
servers = existing_config.setdefault(self.config.mcp_servers_key, {})

if AWS_MCP_SERVER_KEY in servers:
if AWS_KNOWLEDGE_MCP_SERVER_KEY in servers:
return McpConfigureAction.ALREADY_CONFIGURED, config_path

if self.config.mcp_server_entry is not None:
Expand All @@ -235,7 +226,7 @@ def configure_mcp_server(self):
**DEFAULT_MCP_SERVER_CONFIG,
**self.config.mcp_extra_config,
}
servers[AWS_MCP_SERVER_KEY] = server_entry
servers[AWS_KNOWLEDGE_MCP_SERVER_KEY] = server_entry
self._write_mcp_config(config_path, existing_config)
return McpConfigureAction.CONFIGURED, config_path

Expand Down Expand Up @@ -284,6 +275,7 @@ def __init__(self, agent, name, path):
detection_path='~/.claude/',
mcp_config_path='~/.claude.json',
mcp_servers_key='mcpServers',
mcp_extra_config={'type': 'http'},
detection_path_env_override='CLAUDE_CONFIG_DIR',
),
# https://docs.cline.bot/mcp/mcp-overview
Expand All @@ -294,6 +286,10 @@ def __init__(self, agent, name, path):
detection_path='~/.cline/',
mcp_config_path='mcp.json',
mcp_servers_key='mcpServers',
mcp_extra_config={
'type': 'streamableHttp',
'disabled': False,
},
),
# https://developers.openai.com/codex/skills
# https://github.com/openai/codex/blob/main/codex-rs/cli/src/mcp_cmd.rs
Expand All @@ -309,10 +305,9 @@ def __init__(self, agent, name, path):
'codex',
'mcp',
'add',
AWS_MCP_SERVER_KEY,
'--',
'uvx',
*_AWS_MCP_PROXY_ARGS,
AWS_KNOWLEDGE_MCP_SERVER_KEY,
'--url',
AWS_KNOWLEDGE_MCP_SERVER_URL,
],
),
# https://docs.cursor.com/context/model-context-protocol
Expand All @@ -333,6 +328,7 @@ def __init__(self, agent, name, path):
skills_path_override='~/.agents/skills/',
mcp_config_path='settings.json',
mcp_servers_key='mcpServers',
mcp_server_entry={'httpUrl': AWS_KNOWLEDGE_MCP_SERVER_URL},
),
# https://kiro.dev/docs/mcp/configuration/
AgentConfig(
Expand All @@ -341,7 +337,7 @@ def __init__(self, agent, name, path):
detection_path='~/.kiro/',
mcp_config_path='settings/mcp.json',
mcp_servers_key='mcpServers',
mcp_extra_config={'timeout': 100000, 'transport': 'stdio'},
mcp_extra_config={'disabled': False},
),
# https://openclaw-openclaw.mintlify.app/configuration
# OpenClaw does not document MCP support — install skills only.
Expand All @@ -360,8 +356,9 @@ def __init__(self, agent, name, path):
mcp_config_path='opencode.json',
mcp_servers_key='mcp',
mcp_server_entry={
'type': 'local',
'command': ['uvx', *_AWS_MCP_PROXY_ARGS],
'type': 'remote',
'url': AWS_KNOWLEDGE_MCP_SERVER_URL,
'enabled': True,
},
),
# https://github.com/badlogic/pi-mono/blob/main/packages/coding-agent/docs/settings.md
Expand All @@ -380,6 +377,7 @@ def __init__(self, agent, name, path):
skills_path_override='~/.agents/skills/',
mcp_config_path='~/.codeium/mcp_config.json',
mcp_servers_key='mcpServers',
mcp_server_entry={'serverUrl': AWS_KNOWLEDGE_MCP_SERVER_URL},
),
]

Expand Down
9 changes: 5 additions & 4 deletions awscli/customizations/agenttoolkit/configure.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@
class ConfigureAgentToolkitCommand(BasicCommand):
NAME = 'agent-toolkit'
DESCRIPTION = (
'Set up AI coding agents with AWS skills and the AWS MCP Server.\n\n'
'Set up AI coding agents with AWS skills and the AWS Knowledge MCP '
'server.\n\n'
'Supported agents are determined by the presence of configuration '
'directories, such as ``~/.kiro``. Skills are installed globally in '
'those configuration directories, not per project.'
Expand All @@ -47,7 +48,7 @@ class ConfigureAgentToolkitCommand(BasicCommand):
'help_text': (
'Skip all interactive prompts and accept defaults: '
'select all detected agents, install default skills, '
'and configure the AWS MCP server.'
'and configure the AWS Knowledge MCP server.'
),
'default': False,
'action': 'store_true',
Expand Down Expand Up @@ -196,11 +197,11 @@ def _install_default_skills(self, selected_agents, client, yes=False):

def _configure_mcp(self, agents, yes=False):
if not yes and not yes_no_choice(
'\nConfigure AWS MCP server connection? [Y/n]: '
'\nConfigure AWS Knowledge MCP server connection? [Y/n]: '
):
return

uni_print('\nAWS MCP server configured for:\n', self._stream)
uni_print('\nAWS Knowledge MCP server configured for:\n', self._stream)
skipped = []
for agent in agents:
action, detail = agent.configure_mcp_server()
Expand Down
44 changes: 28 additions & 16 deletions tests/unit/customizations/agenttoolkit/test_agents.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,21 +177,31 @@ def test_configure_mcp_new_file(tmp_path):
action, path = agent.configure_mcp_server()
assert action is McpConfigureAction.CONFIGURED
data = json.loads(open(path).read())
assert data['mcpServers']['aws-mcp']['command'] == 'uvx'
assert 'mcp-proxy-for-aws@latest' in data['mcpServers']['aws-mcp']['args']
entry = data['mcpServers']['aws-knowledge-mcp-server']
assert entry == {'url': 'https://knowledge-mcp.global.api.aws'}


def test_configure_mcp_already_configured(tmp_path):
(tmp_path / '.test-agent').mkdir()
agent = make_config(tmp_path, mcp_config_path='mcp.json').detect()
mcp_path = tmp_path / '.test-agent' / 'mcp.json'
mcp_path.write_text(
json.dumps({'mcpServers': {'aws-mcp': {'command': 'custom'}}})
json.dumps(
{
'mcpServers': {
'aws-knowledge-mcp-server': {
'url': 'https://custom.example.com'
}
}
}
)
)
action, _ = agent.configure_mcp_server()
assert action is McpConfigureAction.ALREADY_CONFIGURED
data = json.loads(mcp_path.read_text())
assert data['mcpServers']['aws-mcp']['command'] == 'custom'
assert data['mcpServers']['aws-knowledge-mcp-server'] == {
'url': 'https://custom.example.com'
}


def test_configure_mcp_preserves_other_servers(tmp_path):
Expand All @@ -204,29 +214,31 @@ def test_configure_mcp_preserves_other_servers(tmp_path):
agent.configure_mcp_server()
data = json.loads(mcp_path.read_text())
assert data['mcpServers']['other']['command'] == 'foo'
assert 'aws-mcp' in data['mcpServers']
assert 'aws-knowledge-mcp-server' in data['mcpServers']


def test_configure_mcp_extra_config_merged(tmp_path):
(tmp_path / '.test-agent').mkdir()
agent = make_config(
tmp_path,
mcp_config_path='mcp.json',
mcp_extra_config={'timeout': 100000},
mcp_extra_config={'disabled': False},
).detect()
agent.configure_mcp_server()
mcp_path = tmp_path / '.test-agent' / 'mcp.json'
data = json.loads(mcp_path.read_text())
entry = data['mcpServers']['aws-mcp']
assert entry['timeout'] == 100000
assert entry['command'] == 'uvx'
entry = data['mcpServers']['aws-knowledge-mcp-server']
assert entry == {
'url': 'https://knowledge-mcp.global.api.aws',
'disabled': False,
}


def test_configure_mcp_server_entry_overrides_default(tmp_path):
(tmp_path / '.test-agent').mkdir()
custom_entry = {
'type': 'local',
'command': ['uvx', 'something@latest'],
'type': 'remote',
'url': 'https://custom.example.com',
}
agent = make_config(
tmp_path,
Expand All @@ -237,16 +249,16 @@ def test_configure_mcp_server_entry_overrides_default(tmp_path):
agent.configure_mcp_server()
mcp_path = tmp_path / '.test-agent' / 'mcp.json'
data = json.loads(mcp_path.read_text())
entry = data['mcp']['aws-mcp']
entry = data['mcp']['aws-knowledge-mcp-server']
assert entry == custom_entry
assert 'args' not in entry
assert 'command' not in entry


def test_configure_mcp_shell_command_runs_when_executable_present(tmp_path):
(tmp_path / '.test-agent').mkdir()
config = make_config(
tmp_path,
mcp_shell_command=['some-cli', 'mcp', 'add', 'aws-mcp'],
mcp_shell_command=['some-cli', 'mcp', 'add', 'test-server'],
)
agent = config.detect()
with (
Expand All @@ -262,15 +274,15 @@ def test_configure_mcp_shell_command_runs_when_executable_present(tmp_path):
assert action is McpConfigureAction.CONFIGURED
assert detail == 'some-cli'
run_mock.assert_called_once_with(
['some-cli', 'mcp', 'add', 'aws-mcp'], check=True
['some-cli', 'mcp', 'add', 'test-server'], check=True
)


def test_configure_mcp_shell_command_skipped_when_executable_missing(tmp_path):
(tmp_path / '.test-agent').mkdir()
config = make_config(
tmp_path,
mcp_shell_command=['missing-cli', 'mcp', 'add', 'aws-mcp'],
mcp_shell_command=['missing-cli', 'mcp', 'add', 'test-server'],
)
agent = config.detect()
with (
Expand Down
17 changes: 13 additions & 4 deletions tests/unit/customizations/agenttoolkit/test_configure.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,8 +114,9 @@ def test_mcp_configured_on_yes(tmp_path):
mcp_path = tmp_path / '.agent-0' / 'mcp.json'
assert mcp_path.exists()
data = json.loads(mcp_path.read_text())
assert 'aws-mcp' in data['mcpServers']
assert data['mcpServers']['aws-mcp']['command'] == 'uvx'
assert data['mcpServers']['aws-knowledge-mcp-server'] == {
'url': 'https://knowledge-mcp.global.api.aws'
}


def test_mcp_skipped_on_no(tmp_path):
Expand All @@ -130,12 +131,20 @@ def test_mcp_already_configured_not_overwritten(tmp_path):
mcp_path = tmp_path / '.agent-0' / 'mcp.json'
mcp_path.write_text(
json.dumps(
{'mcpServers': {'aws-mcp': {'command': 'custom', 'args': []}}}
{
'mcpServers': {
'aws-knowledge-mcp-server': {
'url': 'https://custom.example.com'
}
}
}
)
)
_, stream = _run(configs, yes_no_return=True)
data = json.loads(mcp_path.read_text())
assert data['mcpServers']['aws-mcp']['command'] == 'custom'
assert data['mcpServers']['aws-knowledge-mcp-server'] == {
'url': 'https://custom.example.com'
}
assert 'already configured' in stream.getvalue()


Expand Down
Loading