Skip to content

feat(security-agent-mcp-server): Add custom User-Agent - #4319

Open
jigar-lab wants to merge 2 commits into
mainfrom
feat/security-agent-mcp-custom-useragent
Open

feat(security-agent-mcp-server): Add custom User-Agent#4319
jigar-lab wants to merge 2 commits into
mainfrom
feat/security-agent-mcp-custom-useragent

Conversation

@jigar-lab

@jigar-lab jigar-lab commented Jul 22, 2026

Copy link
Copy Markdown

Summary

Adds a custom User-Agent string to all boto3 API calls from the security-agent-mcp-server, enabling service-side tracking of MCP usage by IDE/tool.

User-Agent format

md/awslabs#mcp#security-agent-mcp-server#0.1.2 md/client#kiro/1.5.0

Follows the repo-wide md/awslabs#mcp#<server-name>#<version> convention, with an additional md/client#<name>/<version> suffix for IDE tracking.

How it works

  1. aws_client.py: SecurityAgentClient builds a botocore.config.Config(user_agent_extra=...) and passes it to every .client() call (securityagent, sts, s3, iam). Uses the package __version__ constant directly.
  2. server.py: @ensure_client_ua decorator extracts clientInfo from the MCP session context (sent by the IDE during initialize) and calls set_mcp_client_info() on the shared AWS client singleton.

Design decisions

  • __version__ constant: Uses the package-level __version__ instead of importlib.metadata.version() to avoid silent degradation to "unknown" in editable/source installs.
  • Sanitized client name: clientInfo.name is normalized with .lower().replace(" ", "-") before injection into the UA string, preventing malformed tokens (e.g. "Claude Code" → "claude-code").
  • Decorator pattern: @ensure_client_ua applied to all tool functions as a single choke point — no per-tool boilerplate, and new tools cannot forget it.
  • Lazy injection: The SecurityAgentClient singleton is created at module level before any MCP session connects, so clientInfo is injected on first tool invocation.
  • No-op optimization: set_mcp_client_info() short-circuits if the info has not changed.
  • Graceful fallback: Falls back to unknown when clientInfo is unavailable.
  • Concurrency note: Current stdio transport guarantees one client per process. Under future HTTP/SSE transport with concurrent sessions, _config would need per-session isolation.

Tracking values by IDE

IDE md/client# value
Kiro kiro/<version>
Claude Code claude-code/<version>
Cursor cursor/<version>
Unknown unknown

Tested

  • All 41 unit tests pass (197 total including existing)
  • E2E verified: API call to SecurityAgent service confirmed the custom UA on the wire
  • Works with Kiro, Claude Code, and any MCP-compliant client that sends clientInfo

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of the project license.

@jigar-lab
jigar-lab requested review from a team as code owners July 22, 2026 20:54
@jigar-lab
jigar-lab force-pushed the feat/security-agent-mcp-custom-useragent branch from 5f53f7f to 7f6e306 Compare July 22, 2026 20:55
@codecov

codecov Bot commented Jul 22, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 93.13%. Comparing base (1ea4990) to head (f815bc6).
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #4319   +/-   ##
=======================================
  Coverage   93.12%   93.13%           
=======================================
  Files        1036     1036           
  Lines       87307    87358   +51     
  Branches    14085    14090    +5     
=======================================
+ Hits        81308    81359   +51     
  Misses       3638     3638           
  Partials     2361     2361           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@jigar-lab jigar-lab changed the title feat(security-agent-mcp-server): Add custom User-Agent for API usage tracking feat(security-agent-mcp-server): Add custom User-Agent Jul 22, 2026
@jigar-lab
jigar-lab force-pushed the feat/security-agent-mcp-custom-useragent branch 3 times, most recently from b67f874 to fe66696 Compare July 24, 2026 16:42
Comment thread src/security-agent-mcp-server/awslabs/security_agent_mcp_server/aws_client.py Outdated
@jigar-lab
jigar-lab force-pushed the feat/security-agent-mcp-custom-useragent branch 4 times, most recently from 479bcda to 904af8e Compare July 27, 2026 17:23
@scottschreckengaust
scottschreckengaust requested a review from a team July 27, 2026 18:15

@scottschreckengaust scottschreckengaust left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for adding usage telemetry — the lazy-injection approach is reasonable and every failure path degrades gracefully (no crashes). I reviewed at high effort and verified the technical claims below empirically. Two items keep the PR from delivering its own stated contract and are worth fixing before merge; the rest are cleanup/altitude.

Should fix (contract gaps):

  1. get_api_guide sends an untagged User-Agent. It builds its own session.client('securityagent') without config=, so it's the one .client() site the PR didn't thread the custom UA through — contradicting "passes it to every .client() call." The _ensure_client_ua(ctx) at the top of that tool is also inert, since it mutates the _client singleton this tool bypasses.
  2. Version reports unknown from any uninstalled/editable checkout. importlib.metadata.version('awslabs.security-agent-mcp-server') raises PackageNotFoundError when run from source (verified in this repo), silently degrading to unknown. __version__ = '0.1.2' is already exported by __init__.py, and ~30 other servers in this repo build the UA from that constant. Reusing it also removes the per-rebuild metadata lookup.

Consider (cleanup / consistency):
3. clientInfo.name is injected into the UA unsanitized, unlike _client_prefix which normalizes it — a name like Claude Code produces a broken UA token (botocore does not sanitize user_agent_extra; confirmed).
4. _ensure_client_ua(ctx) is duplicated as the first line of all 11 tools, and duplicates the clientInfo-extraction logic already in _client_prefix (with a divergent fallback: unknown vs ide).
5. The shared mutable _client._config is safe under today's stdio transport but would cross-contaminate the UA across concurrent sessions under HTTP/SSE.

Inline comments below. Nothing here is a crash; posting as a Comment, not a blocking Request Changes.

Comment thread src/security-agent-mcp-server/awslabs/security_agent_mcp_server/server.py Outdated
Comment thread src/security-agent-mcp-server/awslabs/security_agent_mcp_server/aws_client.py Outdated
Comment thread src/security-agent-mcp-server/awslabs/security_agent_mcp_server/aws_client.py Outdated
Comment thread src/security-agent-mcp-server/tests/test_aws_client.py Outdated
@jigar-lab
jigar-lab force-pushed the feat/security-agent-mcp-custom-useragent branch 4 times, most recently from 1b88b92 to 1f99fec Compare July 29, 2026 17:42
…tracking

Add a custom User-Agent string to all boto3 API calls made by the
security-agent-mcp-server. The User-Agent includes:
- MCP server name and version (awslabs-security-agent-mcp-server/X.Y.Z)
- Calling MCP client name and version (e.g. kiro/1.5.0, claude-code/1.0)

This enables service-side tracking of API usage originating from the
local MCP server, broken down by which IDE/tool invoked it.

The client identity is extracted from the MCP protocol clientInfo field
sent during the initialize handshake. Falls back to unknown when
clientInfo is not available.

Changes:
- aws_client.py: Added botocore.config.Config with user_agent_extra
  to all boto3 client calls (securityagent, sts, s3, iam). Added
  set_mcp_client_info() for lazy injection after MCP init.
- server.py: Added _ensure_client_ua(ctx) that extracts clientInfo
  from the MCP session context and injects it into the AWS client
  on the first tool invocation.
… refactor to decorator, fix missed client call

- Use __version__ constant instead of importlib.metadata.version() for reliability
- Sanitize mcp_client_name (lowercase, replace spaces) before injecting into UA
- Add concurrency note about per-session isolation for future HTTP/SSE transport
- Refactor _ensure_client_ua from per-tool boilerplate to @ensure_client_ua decorator
- Fix get_api_guide to pass config=_client._config for tagged UA
- Remove obsolete importlib fallback test
@jigar-lab
jigar-lab force-pushed the feat/security-agent-mcp-custom-useragent branch from 1f99fec to f815bc6 Compare August 10, 2026 20:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: To triage

Development

Successfully merging this pull request may close these issues.

2 participants