cited-cli 0.3.15__tar.gz → 0.5.1__tar.gz
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.
- {cited_cli-0.3.15 → cited_cli-0.5.1}/PKG-INFO +4 -4
- {cited_cli-0.3.15 → cited_cli-0.5.1}/packages/core/pyproject.toml +1 -1
- cited_cli-0.5.1/packages/core/src/cited_core/__init__.py +1 -0
- {cited_cli-0.3.15 → cited_cli-0.5.1}/packages/core/src/cited_core/api/endpoints.py +8 -0
- {cited_cli-0.3.15 → cited_cli-0.5.1}/packages/mcp/README.md +4 -4
- {cited_cli-0.3.15 → cited_cli-0.5.1}/packages/mcp/SKILL.md +3 -3
- {cited_cli-0.3.15 → cited_cli-0.5.1}/packages/mcp/pyproject.toml +2 -2
- {cited_cli-0.3.15 → cited_cli-0.5.1}/packages/mcp/src/cited_mcp/plan_gating.py +17 -0
- {cited_cli-0.3.15 → cited_cli-0.5.1}/packages/mcp/src/cited_mcp/remote.py +56 -2
- {cited_cli-0.3.15 → cited_cli-0.5.1}/packages/mcp/src/cited_mcp/tool_changelog.yaml +170 -0
- {cited_cli-0.3.15 → cited_cli-0.5.1}/packages/mcp/src/cited_mcp/tools/_helpers.py +103 -10
- cited_cli-0.5.1/packages/mcp/src/cited_mcp/tools/business.py +477 -0
- cited_cli-0.5.1/packages/mcp/src/cited_mcp/tools/hq.py +799 -0
- {cited_cli-0.3.15 → cited_cli-0.5.1}/packages/mcp/src/cited_mcp/tools/recommend.py +20 -1
- {cited_cli-0.3.15 → cited_cli-0.5.1}/packages/mcp/tests/test_auth_provider.py +41 -32
- {cited_cli-0.3.15 → cited_cli-0.5.1}/packages/mcp/tests/test_plan_gating.py +74 -1
- cited_cli-0.5.1/packages/mcp/tests/test_surface_invariants.py +332 -0
- {cited_cli-0.3.15 → cited_cli-0.5.1}/packages/mcp/tests/test_tools.py +684 -3
- {cited_cli-0.3.15 → cited_cli-0.5.1}/pyproject.toml +4 -4
- {cited_cli-0.3.15 → cited_cli-0.5.1}/src/cited_cli/commands/business.py +104 -0
- cited_cli-0.5.1/src/cited_cli/commands/hq.py +578 -0
- {cited_cli-0.3.15 → cited_cli-0.5.1}/tests/conftest.py +3 -1
- {cited_cli-0.3.15 → cited_cli-0.5.1}/tests/test_api_client.py +0 -2
- {cited_cli-0.3.15 → cited_cli-0.5.1}/tests/test_auth.py +31 -18
- {cited_cli-0.3.15 → cited_cli-0.5.1}/tests/test_config.py +0 -2
- {cited_cli-0.3.15 → cited_cli-0.5.1}/tests/test_feature_parity.py +45 -26
- {cited_cli-0.3.15 → cited_cli-0.5.1}/tests/test_live_pipeline.py +7 -9
- {cited_cli-0.3.15 → cited_cli-0.5.1}/tests/test_mcp_server.py +18 -0
- {cited_cli-0.3.15 → cited_cli-0.5.1}/tests/test_mcp_tools.py +39 -20
- {cited_cli-0.3.15 → cited_cli-0.5.1}/tests/test_output.py +1 -3
- {cited_cli-0.3.15 → cited_cli-0.5.1}/tests/test_pipeline.py +1138 -248
- cited_cli-0.3.15/packages/core/src/cited_core/__init__.py +0 -1
- cited_cli-0.3.15/packages/mcp/src/cited_mcp/tools/business.py +0 -273
- cited_cli-0.3.15/packages/mcp/src/cited_mcp/tools/hq.py +0 -93
- cited_cli-0.3.15/src/cited_cli/commands/hq.py +0 -125
- {cited_cli-0.3.15 → cited_cli-0.5.1}/.github/workflows/changelog-guard.yml +0 -0
- {cited_cli-0.3.15 → cited_cli-0.5.1}/.github/workflows/ci.yml +0 -0
- {cited_cli-0.3.15 → cited_cli-0.5.1}/.github/workflows/release.yml +0 -0
- {cited_cli-0.3.15 → cited_cli-0.5.1}/.gitignore +0 -0
- {cited_cli-0.3.15 → cited_cli-0.5.1}/CLAUDE.md +0 -0
- {cited_cli-0.3.15 → cited_cli-0.5.1}/DEMO.md +0 -0
- {cited_cli-0.3.15 → cited_cli-0.5.1}/FUTURE.md +0 -0
- {cited_cli-0.3.15 → cited_cli-0.5.1}/LICENSE +0 -0
- {cited_cli-0.3.15 → cited_cli-0.5.1}/README.md +0 -0
- {cited_cli-0.3.15 → cited_cli-0.5.1}/TEST_RUNBOOK.md +0 -0
- {cited_cli-0.3.15 → cited_cli-0.5.1}/cited-plugins/.claude-plugin/marketplace.json +0 -0
- {cited_cli-0.3.15 → cited_cli-0.5.1}/cited-plugins/.claude-plugin/plugin.json +0 -0
- {cited_cli-0.3.15 → cited_cli-0.5.1}/cited-plugins/.mcp.json +0 -0
- {cited_cli-0.3.15 → cited_cli-0.5.1}/cited-plugins/CLAUDE_DESKTOP_SETUP.md +0 -0
- {cited_cli-0.3.15 → cited_cli-0.5.1}/cited-plugins/agents/geo-analyst.md +0 -0
- {cited_cli-0.3.15 → cited_cli-0.5.1}/cited-plugins/commands/audit.md +0 -0
- {cited_cli-0.3.15 → cited_cli-0.5.1}/cited-plugins/commands/recommend.md +0 -0
- {cited_cli-0.3.15 → cited_cli-0.5.1}/cited-plugins/commands/status.md +0 -0
- {cited_cli-0.3.15 → cited_cli-0.5.1}/cited-plugins/settings.json +0 -0
- {cited_cli-0.3.15 → cited_cli-0.5.1}/cited-plugins/skills/business-setup/SKILL.md +0 -0
- {cited_cli-0.3.15 → cited_cli-0.5.1}/cited-plugins/skills/explore/SKILL.md +0 -0
- {cited_cli-0.3.15 → cited_cli-0.5.1}/cited-plugins/skills/geo-audit/SKILL.md +0 -0
- {cited_cli-0.3.15 → cited_cli-0.5.1}/cited-plugins/skills/geo-recommend/SKILL.md +0 -0
- {cited_cli-0.3.15 → cited_cli-0.5.1}/packages/core/src/cited_core/api/__init__.py +0 -0
- {cited_cli-0.3.15 → cited_cli-0.5.1}/packages/core/src/cited_core/api/client.py +0 -0
- {cited_cli-0.3.15 → cited_cli-0.5.1}/packages/core/src/cited_core/auth/__init__.py +0 -0
- {cited_cli-0.3.15 → cited_cli-0.5.1}/packages/core/src/cited_core/auth/oauth_server.py +0 -0
- {cited_cli-0.3.15 → cited_cli-0.5.1}/packages/core/src/cited_core/auth/store.py +0 -0
- {cited_cli-0.3.15 → cited_cli-0.5.1}/packages/core/src/cited_core/config/__init__.py +0 -0
- {cited_cli-0.3.15 → cited_cli-0.5.1}/packages/core/src/cited_core/config/constants.py +0 -0
- {cited_cli-0.3.15 → cited_cli-0.5.1}/packages/core/src/cited_core/config/manager.py +0 -0
- {cited_cli-0.3.15 → cited_cli-0.5.1}/packages/core/src/cited_core/errors.py +0 -0
- {cited_cli-0.3.15 → cited_cli-0.5.1}/packages/mcp/src/cited_mcp/__init__.py +0 -0
- {cited_cli-0.3.15 → cited_cli-0.5.1}/packages/mcp/src/cited_mcp/__main__.py +0 -0
- {cited_cli-0.3.15 → cited_cli-0.5.1}/packages/mcp/src/cited_mcp/auth_provider.py +0 -0
- {cited_cli-0.3.15 → cited_cli-0.5.1}/packages/mcp/src/cited_mcp/context.py +0 -0
- {cited_cli-0.3.15 → cited_cli-0.5.1}/packages/mcp/src/cited_mcp/server.py +0 -0
- {cited_cli-0.3.15 → cited_cli-0.5.1}/packages/mcp/src/cited_mcp/tool_manager.py +0 -0
- {cited_cli-0.3.15 → cited_cli-0.5.1}/packages/mcp/src/cited_mcp/tools/__init__.py +0 -0
- {cited_cli-0.3.15 → cited_cli-0.5.1}/packages/mcp/src/cited_mcp/tools/action_plan.py +0 -0
- {cited_cli-0.3.15 → cited_cli-0.5.1}/packages/mcp/src/cited_mcp/tools/agent.py +0 -0
- {cited_cli-0.3.15 → cited_cli-0.5.1}/packages/mcp/src/cited_mcp/tools/analytics.py +0 -0
- {cited_cli-0.3.15 → cited_cli-0.5.1}/packages/mcp/src/cited_mcp/tools/audit.py +0 -0
- {cited_cli-0.3.15 → cited_cli-0.5.1}/packages/mcp/src/cited_mcp/tools/auth.py +0 -0
- {cited_cli-0.3.15 → cited_cli-0.5.1}/packages/mcp/src/cited_mcp/tools/billing.py +0 -0
- {cited_cli-0.3.15 → cited_cli-0.5.1}/packages/mcp/src/cited_mcp/tools/changelog.py +0 -0
- {cited_cli-0.3.15 → cited_cli-0.5.1}/packages/mcp/src/cited_mcp/tools/job.py +0 -0
- {cited_cli-0.3.15 → cited_cli-0.5.1}/packages/mcp/src/cited_mcp/tools/solution.py +0 -0
- {cited_cli-0.3.15 → cited_cli-0.5.1}/packages/mcp/tests/conftest.py +0 -0
- {cited_cli-0.3.15 → cited_cli-0.5.1}/scripts/dev-install.sh +0 -0
- {cited_cli-0.3.15 → cited_cli-0.5.1}/scripts/mcp_test_harness.py +0 -0
- {cited_cli-0.3.15 → cited_cli-0.5.1}/src/cited_cli/__init__.py +0 -0
- {cited_cli-0.3.15 → cited_cli-0.5.1}/src/cited_cli/__main__.py +0 -0
- {cited_cli-0.3.15 → cited_cli-0.5.1}/src/cited_cli/api/__init__.py +0 -0
- {cited_cli-0.3.15 → cited_cli-0.5.1}/src/cited_cli/api/client.py +0 -0
- {cited_cli-0.3.15 → cited_cli-0.5.1}/src/cited_cli/api/endpoints.py +0 -0
- {cited_cli-0.3.15 → cited_cli-0.5.1}/src/cited_cli/app.py +0 -0
- {cited_cli-0.3.15 → cited_cli-0.5.1}/src/cited_cli/auth/__init__.py +0 -0
- {cited_cli-0.3.15 → cited_cli-0.5.1}/src/cited_cli/auth/oauth_server.py +0 -0
- {cited_cli-0.3.15 → cited_cli-0.5.1}/src/cited_cli/auth/store.py +0 -0
- {cited_cli-0.3.15 → cited_cli-0.5.1}/src/cited_cli/commands/__init__.py +0 -0
- {cited_cli-0.3.15 → cited_cli-0.5.1}/src/cited_cli/commands/agent.py +0 -0
- {cited_cli-0.3.15 → cited_cli-0.5.1}/src/cited_cli/commands/analytics.py +0 -0
- {cited_cli-0.3.15 → cited_cli-0.5.1}/src/cited_cli/commands/audit.py +0 -0
- {cited_cli-0.3.15 → cited_cli-0.5.1}/src/cited_cli/commands/auth.py +0 -0
- {cited_cli-0.3.15 → cited_cli-0.5.1}/src/cited_cli/commands/config_cmd.py +0 -0
- {cited_cli-0.3.15 → cited_cli-0.5.1}/src/cited_cli/commands/job.py +0 -0
- {cited_cli-0.3.15 → cited_cli-0.5.1}/src/cited_cli/commands/mcp_cmd.py +0 -0
- {cited_cli-0.3.15 → cited_cli-0.5.1}/src/cited_cli/commands/named_audit.py +0 -0
- {cited_cli-0.3.15 → cited_cli-0.5.1}/src/cited_cli/commands/recommend.py +0 -0
- {cited_cli-0.3.15 → cited_cli-0.5.1}/src/cited_cli/commands/solution.py +0 -0
- {cited_cli-0.3.15 → cited_cli-0.5.1}/src/cited_cli/config/__init__.py +0 -0
- {cited_cli-0.3.15 → cited_cli-0.5.1}/src/cited_cli/config/constants.py +0 -0
- {cited_cli-0.3.15 → cited_cli-0.5.1}/src/cited_cli/config/manager.py +0 -0
- {cited_cli-0.3.15 → cited_cli-0.5.1}/src/cited_cli/mcp/__init__.py +0 -0
- {cited_cli-0.3.15 → cited_cli-0.5.1}/src/cited_cli/mcp/context.py +0 -0
- {cited_cli-0.3.15 → cited_cli-0.5.1}/src/cited_cli/mcp/server.py +0 -0
- {cited_cli-0.3.15 → cited_cli-0.5.1}/src/cited_cli/mcp/tools/__init__.py +0 -0
- {cited_cli-0.3.15 → cited_cli-0.5.1}/src/cited_cli/mcp/tools/_helpers.py +0 -0
- {cited_cli-0.3.15 → cited_cli-0.5.1}/src/cited_cli/mcp/tools/audit.py +0 -0
- {cited_cli-0.3.15 → cited_cli-0.5.1}/src/cited_cli/mcp/tools/auth.py +0 -0
- {cited_cli-0.3.15 → cited_cli-0.5.1}/src/cited_cli/mcp/tools/business.py +0 -0
- {cited_cli-0.3.15 → cited_cli-0.5.1}/src/cited_cli/mcp/tools/job.py +0 -0
- {cited_cli-0.3.15 → cited_cli-0.5.1}/src/cited_cli/mcp/tools/recommend.py +0 -0
- {cited_cli-0.3.15 → cited_cli-0.5.1}/src/cited_cli/mcp/tools/solution.py +0 -0
- {cited_cli-0.3.15 → cited_cli-0.5.1}/src/cited_cli/output/__init__.py +0 -0
- {cited_cli-0.3.15 → cited_cli-0.5.1}/src/cited_cli/output/formatter.py +0 -0
- {cited_cli-0.3.15 → cited_cli-0.5.1}/src/cited_cli/output/progress.py +0 -0
- {cited_cli-0.3.15 → cited_cli-0.5.1}/src/cited_cli/output/tables.py +0 -0
- {cited_cli-0.3.15 → cited_cli-0.5.1}/src/cited_cli/utils/__init__.py +0 -0
- {cited_cli-0.3.15 → cited_cli-0.5.1}/src/cited_cli/utils/errors.py +0 -0
- {cited_cli-0.3.15 → cited_cli-0.5.1}/src/cited_cli/utils/interactive.py +0 -0
- {cited_cli-0.3.15 → cited_cli-0.5.1}/tests/__init__.py +0 -0
- {cited_cli-0.3.15 → cited_cli-0.5.1}/tests/test_version.py +0 -0
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cited-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.1
|
|
4
4
|
Summary: CLI for the Cited GEO platform
|
|
5
5
|
Author-email: Cited <support@youcited.com>
|
|
6
6
|
License-Expression: LicenseRef-Proprietary
|
|
7
7
|
License-File: LICENSE
|
|
8
8
|
Requires-Python: >=3.11
|
|
9
|
-
Requires-Dist: cited-core>=0.
|
|
9
|
+
Requires-Dist: cited-core>=0.5.1
|
|
10
10
|
Requires-Dist: rich>=13.0.0
|
|
11
11
|
Requires-Dist: typer>=0.15.0
|
|
12
12
|
Provides-Extra: dev
|
|
13
|
-
Requires-Dist: cited-mcp>=0.
|
|
13
|
+
Requires-Dist: cited-mcp>=0.5.1; extra == 'dev'
|
|
14
14
|
Requires-Dist: mypy>=1.10; extra == 'dev'
|
|
15
15
|
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
16
16
|
Requires-Dist: respx>=0.21; extra == 'dev'
|
|
17
17
|
Requires-Dist: ruff>=0.4; extra == 'dev'
|
|
18
18
|
Requires-Dist: types-pyyaml>=6.0; extra == 'dev'
|
|
19
19
|
Provides-Extra: mcp
|
|
20
|
-
Requires-Dist: cited-mcp>=0.
|
|
20
|
+
Requires-Dist: cited-mcp>=0.5.1; extra == 'mcp'
|
|
21
21
|
Description-Content-Type: text/markdown
|
|
22
22
|
|
|
23
23
|
# Cited CLI
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.5.1"
|
|
@@ -23,6 +23,7 @@ BUSINESS = "/businesses/{business_id}"
|
|
|
23
23
|
BUSINESS_LOGO = "/businesses/{business_id}/logo"
|
|
24
24
|
HEALTH_SCORES = "/businesses/{business_id}/health-scores"
|
|
25
25
|
HEALTH_SCORES_BREAKDOWN = "/businesses/{business_id}/health-scores/breakdown"
|
|
26
|
+
PROFILE_COMPETITORS = "/businesses/{business_id}/profile-competitors"
|
|
26
27
|
|
|
27
28
|
# Business Crawl
|
|
28
29
|
CRAWL_DATA = "/businesses/{business_id}/crawl-data"
|
|
@@ -77,9 +78,16 @@ HQ_PRIORITY_STATUS = "/businesses/{business_id}/hq/priority/{action_id}/status"
|
|
|
77
78
|
HQ_PRIORITY_SUMMARY = "/businesses/{business_id}/hq/priority/summary"
|
|
78
79
|
HQ_EXPORT_PDF = "/businesses/{business_id}/hq/export/pdf"
|
|
79
80
|
HQ_RECOMPUTE = "/businesses/{business_id}/hq/recompute"
|
|
81
|
+
HQ_OVERVIEW_REFRESH_AUDIT = "/businesses/{business_id}/overview/refresh-audit"
|
|
82
|
+
HQ_OVERVIEW_REFRESH_RECS = "/businesses/{business_id}/overview/refresh-recommendations"
|
|
83
|
+
HQ_OVERVIEW_REFRESH_ALL = "/businesses/{business_id}/overview/refresh-all"
|
|
84
|
+
HQ_AGENT_BRIEF = "/businesses/{business_id}/agent-brief"
|
|
80
85
|
PERSONAS = "/businesses/{business_id}/personas"
|
|
86
|
+
PERSONA = "/businesses/{business_id}/personas/{persona_id}"
|
|
81
87
|
PRODUCTS = "/businesses/{business_id}/products"
|
|
88
|
+
PRODUCT = "/businesses/{business_id}/products/{product_id}"
|
|
82
89
|
BUYER_INTENTS = "/businesses/{business_id}/buyer-intents"
|
|
90
|
+
BUYER_INTENT = "/businesses/{business_id}/buyer-intents/{intent_id}"
|
|
83
91
|
AGENTIC_READINESS = "/businesses/{business_id}/agentic-readiness"
|
|
84
92
|
TRUST_SIGNALS = "/businesses/{business_id}/trust-signals"
|
|
85
93
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# cited-mcp
|
|
2
2
|
|
|
3
|
-
MCP server for the [Cited](https://youcited.com) Generative Engine Optimization (GEO) platform. Exposes
|
|
3
|
+
MCP server for the [Cited](https://youcited.com) Generative Engine Optimization (GEO) platform. Exposes 73 tools that let AI assistants like Claude manage businesses, run GEO audits, generate recommendations, work a prioritized action plan, verify fixes via the Validation Engine, and create solutions — all through the [Model Context Protocol](https://modelcontextprotocol.io/).
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
@@ -231,9 +231,9 @@ Tools are gated by subscription tier. All tiers can read data; write operations
|
|
|
231
231
|
|
|
232
232
|
| Tier | Tools Available |
|
|
233
233
|
|------|----------------|
|
|
234
|
-
| **Growth** (entry) | Auth, list/get businesses, crawl, health scores, audits (start/status/result/list), all recommendation tools, action-plan reads (get_action_plan, get_quick_wins), Validation Engine reads, job status — **
|
|
235
|
-
| **Scale** | Everything in Growth + create/update/delete businesses, create/update/delete audit templates, all solution tools, batch solutions, export audit, cancel job, action-plan writes (mark_action_done, dismiss_action, get_action_progress) — **
|
|
236
|
-
| **Pro** | Everything in Scale + usage stats, HQ dashboard, analytics, agent API — **
|
|
234
|
+
| **Growth** (entry) | Auth, list/get businesses, crawl, health scores, audits (start/status/result/list), all recommendation tools, action-plan reads (get_action_plan, get_quick_wins), Validation Engine reads, job status — **31 tools** |
|
|
235
|
+
| **Scale** | Everything in Growth + create/update/delete businesses, create/update/delete audit templates, all solution tools, batch solutions, export audit, cancel job, action-plan writes (mark_action_done, dismiss_action, get_action_progress) — **62 tools** |
|
|
236
|
+
| **Pro** | Everything in Scale + usage stats, HQ dashboard, analytics, agent API — **73 tools** |
|
|
237
237
|
|
|
238
238
|
When a user calls a tool above their plan, the server returns a structured error with an upgrade link:
|
|
239
239
|
|
|
@@ -50,9 +50,9 @@ The typical flow is: **Business Setup → Audit → Recommendations → Solution
|
|
|
50
50
|
|
|
51
51
|
Tools are gated by subscription tier. If a tool is above the user's plan, it returns an upgrade message with a billing link.
|
|
52
52
|
|
|
53
|
-
- **Growth** (entry tier): Read-only — list/get businesses, run audits, view recommendations (
|
|
54
|
-
- **Scale**: Full write access — create/update/delete, solutions, export, cancel (
|
|
55
|
-
- **Pro**: Everything + HQ dashboard, analytics, agent API (
|
|
53
|
+
- **Growth** (entry tier): Read-only — list/get businesses, run audits, view recommendations (31 tools)
|
|
54
|
+
- **Scale**: Full write access — create/update/delete, solutions, export, cancel (62 tools)
|
|
55
|
+
- **Pro**: Everything + HQ dashboard, analytics, agent API (73 tools)
|
|
56
56
|
|
|
57
57
|
Always call `check_auth_status` first — it shows the user's plan and remaining limits.
|
|
58
58
|
|
|
@@ -4,14 +4,14 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "cited-mcp"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.5.1"
|
|
8
8
|
description = "MCP server for the Cited GEO platform"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "LicenseRef-Proprietary"
|
|
11
11
|
requires-python = ">=3.11"
|
|
12
12
|
authors = [{ name = "Cited", email = "support@youcited.com" }]
|
|
13
13
|
dependencies = [
|
|
14
|
-
"cited-core>=0.
|
|
14
|
+
"cited-core>=0.5.1",
|
|
15
15
|
"mcp>=1.27.0",
|
|
16
16
|
"PyJWT>=2.8.0",
|
|
17
17
|
"PyYAML>=6.0",
|
|
@@ -43,6 +43,18 @@ _SCALE_TOOLS: set[str] = {
|
|
|
43
43
|
"mark_action_done",
|
|
44
44
|
"dismiss_action",
|
|
45
45
|
"get_action_progress",
|
|
46
|
+
"set_profile_competitors",
|
|
47
|
+
"create_persona",
|
|
48
|
+
"update_persona",
|
|
49
|
+
"delete_persona",
|
|
50
|
+
"create_product",
|
|
51
|
+
"update_product",
|
|
52
|
+
"delete_product",
|
|
53
|
+
"create_buyer_intent",
|
|
54
|
+
"update_buyer_intent",
|
|
55
|
+
"delete_buyer_intent",
|
|
56
|
+
"recompute_health_scores",
|
|
57
|
+
"refresh_business_overview",
|
|
46
58
|
}
|
|
47
59
|
|
|
48
60
|
# Tools restricted to pro+ tier
|
|
@@ -54,6 +66,7 @@ _PRO_TOOLS: set[str] = {
|
|
|
54
66
|
"get_semantic_health",
|
|
55
67
|
"buyer_fit_query",
|
|
56
68
|
"get_business_hq",
|
|
69
|
+
"get_agent_brief",
|
|
57
70
|
"get_analytics_trends",
|
|
58
71
|
"get_analytics_dashboard",
|
|
59
72
|
"compare_audits",
|
|
@@ -156,6 +169,10 @@ _BASE_TOOLS: set[str] = {
|
|
|
156
169
|
"get_business",
|
|
157
170
|
"crawl_business",
|
|
158
171
|
"get_health_scores",
|
|
172
|
+
"list_profile_competitors",
|
|
173
|
+
"list_personas",
|
|
174
|
+
"list_products",
|
|
175
|
+
"list_buyer_intents",
|
|
159
176
|
"list_audit_templates",
|
|
160
177
|
"get_audit_template",
|
|
161
178
|
"start_audit",
|
|
@@ -204,6 +204,56 @@ class _OAuthEventLogMiddleware:
|
|
|
204
204
|
logger.info("[oauth-event] %s", kv)
|
|
205
205
|
|
|
206
206
|
|
|
207
|
+
class _RequestContextMiddleware:
|
|
208
|
+
"""ASGI middleware that captures the request's source IP and User-Agent
|
|
209
|
+
into contextvars consumed by the tool-call decorator.
|
|
210
|
+
|
|
211
|
+
Why: ``tool_call`` log lines previously carried no source-identifying
|
|
212
|
+
metadata beyond the (possibly absent) decoded user JWT. When a probe
|
|
213
|
+
came in with a non-standard token — logged as user=``unknown`` — there
|
|
214
|
+
was no way to trace the source. Threading an HTTP request object
|
|
215
|
+
through every tool function is invasive; contextvars stay scoped to
|
|
216
|
+
the request's async task without touching tool signatures.
|
|
217
|
+
|
|
218
|
+
For stdio transport this middleware is not in the chain, so the
|
|
219
|
+
contextvars stay None and the log fields render as null.
|
|
220
|
+
|
|
221
|
+
We prefer the leftmost value of ``X-Forwarded-For`` when present
|
|
222
|
+
because the ALB inserts the original client IP there and the immediate
|
|
223
|
+
``scope["client"]`` is the ALB's internal address. Trusting X-F-F is
|
|
224
|
+
only safe behind a known proxy — which is the deploy topology here.
|
|
225
|
+
"""
|
|
226
|
+
|
|
227
|
+
def __init__(self, app: ASGIApp) -> None:
|
|
228
|
+
self.app = app
|
|
229
|
+
|
|
230
|
+
async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
|
|
231
|
+
if scope["type"] != "http":
|
|
232
|
+
await self.app(scope, receive, send)
|
|
233
|
+
return
|
|
234
|
+
|
|
235
|
+
from cited_mcp.tools._helpers import _request_ip, _request_ua
|
|
236
|
+
|
|
237
|
+
headers = {k: v for k, v in scope.get("headers", [])}
|
|
238
|
+
xff = headers.get(b"x-forwarded-for", b"").decode("latin-1", errors="replace")
|
|
239
|
+
if xff:
|
|
240
|
+
ip: str | None = xff.split(",")[0].strip() or None
|
|
241
|
+
else:
|
|
242
|
+
client = scope.get("client") or ("?", 0)
|
|
243
|
+
ip = client[0] if client and client[0] != "?" else None
|
|
244
|
+
|
|
245
|
+
ua_bytes = headers.get(b"user-agent", b"")
|
|
246
|
+
ua = ua_bytes.decode("latin-1", errors="replace")[:120] if ua_bytes else None
|
|
247
|
+
|
|
248
|
+
token_ip = _request_ip.set(ip)
|
|
249
|
+
token_ua = _request_ua.set(ua)
|
|
250
|
+
try:
|
|
251
|
+
await self.app(scope, receive, send)
|
|
252
|
+
finally:
|
|
253
|
+
_request_ip.reset(token_ip)
|
|
254
|
+
_request_ua.reset(token_ua)
|
|
255
|
+
|
|
256
|
+
|
|
207
257
|
@asynccontextmanager
|
|
208
258
|
async def cited_remote_lifespan(server: FastMCP) -> AsyncIterator[CitedContext]:
|
|
209
259
|
"""Lifespan for the remote MCP server.
|
|
@@ -403,9 +453,13 @@ def run_remote_server() -> None:
|
|
|
403
453
|
starlette_app = server.streamable_http_app()
|
|
404
454
|
# Order: oauth-event log on the OUTSIDE of the registration patcher so
|
|
405
455
|
# patcher-rejected /register calls (400 invalid_redirect_uri) still
|
|
406
|
-
# produce an event line.
|
|
456
|
+
# produce an event line. RequestContextMiddleware sits innermost so
|
|
457
|
+
# its contextvars (ip, user_agent) are set in the same task that
|
|
458
|
+
# dispatches the tool call.
|
|
407
459
|
patched_app = _OAuthEventLogMiddleware(
|
|
408
|
-
_PatchRegistrationMiddleware(
|
|
460
|
+
_PatchRegistrationMiddleware(
|
|
461
|
+
_RequestContextMiddleware(starlette_app)
|
|
462
|
+
)
|
|
409
463
|
)
|
|
410
464
|
|
|
411
465
|
config = uvicorn.Config(
|
|
@@ -21,6 +21,176 @@
|
|
|
21
21
|
# this file.
|
|
22
22
|
|
|
23
23
|
versions:
|
|
24
|
+
- version: "0.5.1"
|
|
25
|
+
released: "2026-05-18"
|
|
26
|
+
# Telemetry-only release: tool surface is unchanged (fingerprint matches
|
|
27
|
+
# 0.5.0). Adds forensic fields to every tool_call log line — ip,
|
|
28
|
+
# user_agent, and a new auth_kind discriminator (anonymous / agent_key /
|
|
29
|
+
# oauth_email / oauth_no_email / oauth_malformed / stdio_email /
|
|
30
|
+
# stdio_no_email / stdio_malformed). Also fixes client_id extraction
|
|
31
|
+
# which was reading from user_jwt claims (always null in prod) instead
|
|
32
|
+
# of the OAuth access token. Motivated by a 2026-05-02 dev probe that
|
|
33
|
+
# logged user=unknown with no traceable source.
|
|
34
|
+
fingerprint: "5bef45100c53"
|
|
35
|
+
|
|
36
|
+
- version: "0.5.0"
|
|
37
|
+
released: "2026-05-11"
|
|
38
|
+
# Three new read-only list_* tools to enable persona/product/buyer-intent
|
|
39
|
+
# CRUD flows (agents need IDs before they can call update/delete), plus a
|
|
40
|
+
# significant parameter-schema expansion on update_business / create_business
|
|
41
|
+
# to cover the full canonical BusinessUpdate/CreateRequest field set. Also
|
|
42
|
+
# fixes a latent bug: update_business previously sent HTTP PUT against a
|
|
43
|
+
# backend PATCH route. Bumps minor (not patch) because update_business /
|
|
44
|
+
# create_business signature changes shift parameter ordering and clients
|
|
45
|
+
# diffing the fingerprint should pick this up as non-additive.
|
|
46
|
+
fingerprint: "5bef45100c53"
|
|
47
|
+
tools_added:
|
|
48
|
+
- name: list_personas
|
|
49
|
+
description: |
|
|
50
|
+
List buyer personas declared on the business profile. Required
|
|
51
|
+
before update_persona / delete_persona since the agent has no
|
|
52
|
+
other way to discover persona_ids.
|
|
53
|
+
- name: list_products
|
|
54
|
+
description: |
|
|
55
|
+
List products / service offerings declared on the business
|
|
56
|
+
profile. Required before update_product / delete_product to
|
|
57
|
+
discover product_ids.
|
|
58
|
+
- name: list_buyer_intents
|
|
59
|
+
description: |
|
|
60
|
+
List buyer-intent entries declared on the business profile.
|
|
61
|
+
Use before recommending a new create_buyer_intent to avoid
|
|
62
|
+
overlapping entries.
|
|
63
|
+
- name: update_buyer_intent
|
|
64
|
+
description: |
|
|
65
|
+
Update a buyer-intent entry (question, intent_type, priority,
|
|
66
|
+
is_answered, answering_page_url — all optional). Closes the
|
|
67
|
+
persona/product CRUD symmetry. Requires the cited backend with
|
|
68
|
+
PATCH /businesses/{id}/buyer-intents/{intent_id} (added in
|
|
69
|
+
companion backend PR).
|
|
70
|
+
- name: delete_buyer_intent
|
|
71
|
+
description: |
|
|
72
|
+
Delete a buyer intent. Confirm with the user before calling —
|
|
73
|
+
there's no undo. Requires the cited backend with
|
|
74
|
+
DELETE /businesses/{id}/buyer-intents/{intent_id}.
|
|
75
|
+
tools_changed:
|
|
76
|
+
- name: update_business
|
|
77
|
+
change_summary: |
|
|
78
|
+
Parameter set expanded from 4 fields (name, website, description,
|
|
79
|
+
industry) to the full BusinessUpdateRequest schema: adds
|
|
80
|
+
one_line_description, primary_customer, problem_solved,
|
|
81
|
+
offering_type, primary_market, service_areas,
|
|
82
|
+
headquarters_country, goals, discovery_channels,
|
|
83
|
+
success_definition, plus primary-location fields (address, city,
|
|
84
|
+
region, country). All new params are optional and keyword-only.
|
|
85
|
+
Also fixes a method bug: now sends PATCH (was PUT) to match the
|
|
86
|
+
backend @router.patch route.
|
|
87
|
+
- name: create_business
|
|
88
|
+
change_summary: |
|
|
89
|
+
Parameter set expanded to the full BusinessCreateRequest schema:
|
|
90
|
+
description is now optional (server composes from structured
|
|
91
|
+
fields if omitted); adds one_line_description, primary_customer,
|
|
92
|
+
problem_solved, offering_type, primary_market, service_areas,
|
|
93
|
+
headquarters_country, goals, discovery_channels,
|
|
94
|
+
success_definition. Existing 4-field callers unaffected
|
|
95
|
+
(description was already accepted; making it optional is
|
|
96
|
+
backward-compatible).
|
|
97
|
+
|
|
98
|
+
- version: "0.4.0"
|
|
99
|
+
released: "2026-05-08"
|
|
100
|
+
# 12 new tools + docstring/parameter-schema edits on 4 existing tools —
|
|
101
|
+
# significant surface expansion, so the minor version bumps to 0.4.0
|
|
102
|
+
# to signal a non-additive-only release for clients that diff the
|
|
103
|
+
# fingerprint.
|
|
104
|
+
fingerprint: "8197f97dcd06"
|
|
105
|
+
tools_added:
|
|
106
|
+
- name: get_agent_brief
|
|
107
|
+
description: |
|
|
108
|
+
Single-call composite "what should I focus on right now?" —
|
|
109
|
+
top priority actions, quick wins, failing deterministic checks,
|
|
110
|
+
and recent citation-rate movement. Designed to replace
|
|
111
|
+
chained get_business_hq + get_quick_wins + check_status calls
|
|
112
|
+
in opening-turn agent flows.
|
|
113
|
+
- name: list_profile_competitors
|
|
114
|
+
description: |
|
|
115
|
+
List the user-declared competitors on a business profile.
|
|
116
|
+
These bypass third-party domain filtering and rank ahead of
|
|
117
|
+
citation-only picks in head-to-head comparisons.
|
|
118
|
+
- name: set_profile_competitors
|
|
119
|
+
description: |
|
|
120
|
+
Replace the full list of profile competitors (max 10).
|
|
121
|
+
Pass [{name, website}] entries; PUT replaces. Use when the
|
|
122
|
+
user reports an obviously-relevant rival missing from H2H.
|
|
123
|
+
- name: recompute_health_scores
|
|
124
|
+
description: |
|
|
125
|
+
Force a fresh recomputation of business health scores after
|
|
126
|
+
profile/HQ edits — no crawl, no audit, just re-derive from
|
|
127
|
+
existing data.
|
|
128
|
+
- name: refresh_business_overview
|
|
129
|
+
description: |
|
|
130
|
+
Refresh cached audit / recommendation overview projections
|
|
131
|
+
for a business. scope ∈ {audit, recommendations, all}.
|
|
132
|
+
- name: create_persona
|
|
133
|
+
description: Create a target buyer persona on a business.
|
|
134
|
+
- name: update_persona
|
|
135
|
+
description: Update a persona (partial — only changed fields).
|
|
136
|
+
- name: delete_persona
|
|
137
|
+
description: Delete a persona.
|
|
138
|
+
- name: create_product
|
|
139
|
+
description: Create a product/service offering on a business.
|
|
140
|
+
- name: update_product
|
|
141
|
+
description: Update a product (partial).
|
|
142
|
+
- name: delete_product
|
|
143
|
+
description: Delete a product.
|
|
144
|
+
- name: create_buyer_intent
|
|
145
|
+
description: |
|
|
146
|
+
Create a buyer-intent entry tying personas to products
|
|
147
|
+
through specific decision moments — drives audit question
|
|
148
|
+
generation and per-intent coverage scoring.
|
|
149
|
+
tools_changed:
|
|
150
|
+
- name: get_business_hq
|
|
151
|
+
change_summary: |
|
|
152
|
+
New include flags ``include_agentic_readiness`` and
|
|
153
|
+
``include_trust_signals`` add the agentic-readiness summary
|
|
154
|
+
and trust-signal evidence to the dashboard payload. Existing
|
|
155
|
+
callers are unaffected — both default to false.
|
|
156
|
+
- name: get_competitive_comparison
|
|
157
|
+
change_summary: |
|
|
158
|
+
Docstring now describes the new ``analysis_status`` and
|
|
159
|
+
``last_analysis_run_at`` freshness fields on the response
|
|
160
|
+
(added in cited backend commit 2c716be). Tool behavior is
|
|
161
|
+
unchanged — these were already passed through; the docstring
|
|
162
|
+
was previously silent about them.
|
|
163
|
+
- name: get_recommendation_check_status
|
|
164
|
+
change_summary: |
|
|
165
|
+
Updated for Validation Engine Tier 4: now runs 72
|
|
166
|
+
deterministic checks (was 62). New checks fill
|
|
167
|
+
monitoring_adaptation and original_research domains
|
|
168
|
+
(knowledge_graph_linkage, aggregate_rating_present,
|
|
169
|
+
author_bio_on_blog_posts, sitemap_lastmod_freshness,
|
|
170
|
+
outbound_authority_citations, hreflang_and_lang_consistency,
|
|
171
|
+
well_known_agent_discovery). Response shape unchanged.
|
|
172
|
+
- name: crawl_business
|
|
173
|
+
change_summary: |
|
|
174
|
+
Docstring now notes the post-crawl auto-fill side effect:
|
|
175
|
+
empty business-profile fields (description, founding year,
|
|
176
|
+
NAICS, sameAs links) are populated from JSON-LD / meta tags
|
|
177
|
+
found on the homepage. User-supplied values are never
|
|
178
|
+
overwritten. (cited backend CIT-148.)
|
|
179
|
+
tools_removed: []
|
|
180
|
+
behavior_changed:
|
|
181
|
+
- area: plan_gating
|
|
182
|
+
change_summary: |
|
|
183
|
+
Tier assignments for the new tools:
|
|
184
|
+
- base (all tiers): ``list_profile_competitors``
|
|
185
|
+
- scale: ``set_profile_competitors``,
|
|
186
|
+
``create/update/delete_persona``,
|
|
187
|
+
``create/update/delete_product``,
|
|
188
|
+
``create_buyer_intent``,
|
|
189
|
+
``recompute_health_scores``,
|
|
190
|
+
``refresh_business_overview``
|
|
191
|
+
- pro: ``get_agent_brief`` (matches the ``get_business_hq``
|
|
192
|
+
tier — same composite-dashboard semantics).
|
|
193
|
+
|
|
24
194
|
- version: "0.3.15"
|
|
25
195
|
released: "2026-05-05"
|
|
26
196
|
# Fingerprint unchanged from 0.3.14 image runtime — pure observability +
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import contextlib
|
|
4
|
+
import contextvars
|
|
4
5
|
import functools
|
|
5
6
|
import hashlib
|
|
6
7
|
import json
|
|
@@ -22,6 +23,20 @@ from cited_mcp.context import CitedContext
|
|
|
22
23
|
|
|
23
24
|
logger = logging.getLogger("cited_mcp.usage")
|
|
24
25
|
|
|
26
|
+
# ---------------------------------------------------------------------------
|
|
27
|
+
# Per-request forensic context (set by remote.py _RequestContextMiddleware).
|
|
28
|
+
#
|
|
29
|
+
# These let tool_call log lines carry the request's source IP and User-Agent
|
|
30
|
+
# without threading an HTTP request object through every tool function. For
|
|
31
|
+
# stdio transport (no HTTP) they remain None and are emitted as null in logs.
|
|
32
|
+
# ---------------------------------------------------------------------------
|
|
33
|
+
_request_ip: contextvars.ContextVar[str | None] = contextvars.ContextVar(
|
|
34
|
+
"cited_mcp_request_ip", default=None
|
|
35
|
+
)
|
|
36
|
+
_request_ua: contextvars.ContextVar[str | None] = contextvars.ContextVar(
|
|
37
|
+
"cited_mcp_request_ua", default=None
|
|
38
|
+
)
|
|
39
|
+
|
|
25
40
|
# ---------------------------------------------------------------------------
|
|
26
41
|
# Response size guardrails
|
|
27
42
|
# ---------------------------------------------------------------------------
|
|
@@ -244,18 +259,84 @@ def _extract_user(token: str | None) -> str:
|
|
|
244
259
|
return "unknown"
|
|
245
260
|
|
|
246
261
|
|
|
247
|
-
def
|
|
248
|
-
"""
|
|
249
|
-
|
|
262
|
+
def _extract_client_id_from_oauth() -> str | None:
|
|
263
|
+
"""Return the OAuth client_id from the current access token, truncated.
|
|
264
|
+
|
|
265
|
+
The MCP server's client_id is itself a signed JWT (RFC 7591 dynamic
|
|
266
|
+
registration encoded into the id), so we truncate to a 12-char prefix
|
|
267
|
+
sufficient to group connector traffic without bloating log lines.
|
|
268
|
+
|
|
269
|
+
Returns None for stdio transport (no OAuth context), or whenever the
|
|
270
|
+
access-token contextvar is unset.
|
|
271
|
+
|
|
272
|
+
Note: this used to decode the user_jwt looking for client_id/aud/azp
|
|
273
|
+
claims, but backend session JWTs do not carry OAuth-client identity —
|
|
274
|
+
so the field was always empty in production logs. This implementation
|
|
275
|
+
reads from the OAuth access token instead.
|
|
276
|
+
"""
|
|
277
|
+
try:
|
|
278
|
+
from mcp.server.auth.middleware.auth_context import get_access_token
|
|
279
|
+
except ImportError:
|
|
250
280
|
return None
|
|
251
281
|
try:
|
|
252
|
-
|
|
253
|
-
cid = payload.get("client_id") or payload.get("aud") or payload.get("azp")
|
|
254
|
-
if isinstance(cid, list):
|
|
255
|
-
cid = cid[0] if cid else None
|
|
256
|
-
return str(cid) if cid else None
|
|
282
|
+
access_token = get_access_token()
|
|
257
283
|
except Exception:
|
|
258
284
|
return None
|
|
285
|
+
if not access_token:
|
|
286
|
+
return None
|
|
287
|
+
cid = getattr(access_token, "client_id", None)
|
|
288
|
+
if not cid:
|
|
289
|
+
return None
|
|
290
|
+
return str(cid)[:12]
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
def _extract_auth_kind(
|
|
294
|
+
ctx: Context[Any, CitedContext, Any], token: str | None
|
|
295
|
+
) -> str:
|
|
296
|
+
"""Categorize the auth path for forensic logging.
|
|
297
|
+
|
|
298
|
+
Returns one of:
|
|
299
|
+
- ``anonymous`` — no token, no agent key
|
|
300
|
+
- ``agent_key`` — CitedClient has agent_api_key set (stdio mode)
|
|
301
|
+
- ``oauth_email`` — OAuth flow, user_jwt has email claim
|
|
302
|
+
- ``oauth_no_email`` — OAuth flow, user_jwt decoded but no email
|
|
303
|
+
- ``oauth_malformed`` — OAuth flow, user_jwt couldn't be decoded
|
|
304
|
+
- ``stdio_email`` — stdio mode, token is a JWT with email claim
|
|
305
|
+
- ``stdio_no_email`` — stdio mode, token decoded but no email
|
|
306
|
+
- ``stdio_malformed`` — stdio mode, token isn't a parseable JWT
|
|
307
|
+
|
|
308
|
+
Lets us tell apart the practical cases — including the case where
|
|
309
|
+
``_extract_user`` returns ``"unknown"`` because the token was not a
|
|
310
|
+
JWT at all versus because the JWT had no email/sub claim. Without
|
|
311
|
+
this, the ``<unknown>`` bucket in usage reports is unactionable.
|
|
312
|
+
"""
|
|
313
|
+
try:
|
|
314
|
+
lc = ctx.request_context.lifespan_context
|
|
315
|
+
if getattr(lc.client, "agent_api_key", None):
|
|
316
|
+
return "agent_key"
|
|
317
|
+
except Exception:
|
|
318
|
+
pass
|
|
319
|
+
|
|
320
|
+
method = "stdio"
|
|
321
|
+
try:
|
|
322
|
+
from mcp.server.auth.middleware.auth_context import get_access_token
|
|
323
|
+
|
|
324
|
+
if get_access_token() is not None:
|
|
325
|
+
method = "oauth"
|
|
326
|
+
except Exception:
|
|
327
|
+
pass
|
|
328
|
+
|
|
329
|
+
if not token:
|
|
330
|
+
return "anonymous" if method == "stdio" else "oauth_no_token"
|
|
331
|
+
|
|
332
|
+
try:
|
|
333
|
+
payload = pyjwt.decode(token, options={"verify_signature": False})
|
|
334
|
+
except Exception:
|
|
335
|
+
return f"{method}_malformed"
|
|
336
|
+
|
|
337
|
+
if payload.get("email"):
|
|
338
|
+
return f"{method}_email"
|
|
339
|
+
return f"{method}_no_email"
|
|
259
340
|
|
|
260
341
|
|
|
261
342
|
# ---------------------------------------------------------------------------
|
|
@@ -384,7 +465,8 @@ def log_tool_call(
|
|
|
384
465
|
token = _resolve_token(ctx)
|
|
385
466
|
rl_key = _rate_limit_key(token)
|
|
386
467
|
user = _extract_user(token)
|
|
387
|
-
|
|
468
|
+
auth_kind = _extract_auth_kind(ctx, token)
|
|
469
|
+
client_id = _extract_client_id_from_oauth()
|
|
388
470
|
request_id = uuid.uuid4().hex[:12]
|
|
389
471
|
start = time.monotonic()
|
|
390
472
|
build = _get_build_identity()
|
|
@@ -399,7 +481,10 @@ def log_tool_call(
|
|
|
399
481
|
"request_id": request_id,
|
|
400
482
|
"tool": tool_name,
|
|
401
483
|
"user": user,
|
|
484
|
+
"auth_kind": auth_kind,
|
|
402
485
|
"client_id": client_id,
|
|
486
|
+
"ip": _request_ip.get(),
|
|
487
|
+
"user_agent": _request_ua.get(),
|
|
403
488
|
"server_version": build["server_version"],
|
|
404
489
|
"tools_fingerprint": build["tools_fingerprint"],
|
|
405
490
|
"deployment_id": build["deployment_id"],
|
|
@@ -407,7 +492,15 @@ def log_tool_call(
|
|
|
407
492
|
if resources:
|
|
408
493
|
payload["resources"] = resources
|
|
409
494
|
payload.update(extra)
|
|
410
|
-
|
|
495
|
+
# CodeQL flags this as clear-text logging of sensitive data because
|
|
496
|
+
# `user` is sourced from a JWT email claim. This is by design —
|
|
497
|
+
# operator usage reports (scripts/mcp_usage_report.py) attribute
|
|
498
|
+
# tool calls to users by email, which is necessary for forensics
|
|
499
|
+
# and abuse investigation. Logs flow to a private CloudWatch group
|
|
500
|
+
# with restricted IAM access. Email is not transmitted anywhere
|
|
501
|
+
# else from this line. If we later move to hashed identifiers,
|
|
502
|
+
# the suppression can be removed.
|
|
503
|
+
logger.info(json.dumps(payload, default=str)) # noqa: E501 lgtm[py/clear-text-logging-sensitive-data]
|
|
411
504
|
|
|
412
505
|
# Rate limit check
|
|
413
506
|
if rl_err := _check_rate_limit(rl_key):
|