Skip to content

Commit 1ec984a

Browse files
committed
feat: send current timestamp for MCP live activity
1 parent 028ce3a commit 1ec984a

6 files changed

Lines changed: 18 additions & 5 deletions

File tree

ggshield/verticals/ai/agents/claude_code.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,7 @@ def parse_mcp_activity(
243243
model="",
244244
cwd=payload.raw.get("cwd", ""),
245245
input=payload.raw.get("tool_input", {}),
246+
timestamp=payload.timestamp,
246247
)
247248

248249
def iter_history_events(

ggshield/verticals/ai/agents/codex.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,4 +124,5 @@ def parse_mcp_activity(
124124
model=payload.raw.get("model", ""),
125125
cwd=payload.raw.get("cwd", ""),
126126
input=payload.raw.get("tool_input", {}),
127+
timestamp=payload.timestamp,
127128
)

ggshield/verticals/ai/agents/cursor.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
Transport,
2929
)
3030

31+
3132
logger = logging.getLogger(__name__)
3233

3334

@@ -324,6 +325,7 @@ def parse_mcp_activity(
324325
model=payload.raw.get("model", ""),
325326
cwd=payload.raw.get("workspace_roots", [""])[0],
326327
input=payload.raw.get("tool_input", {}),
328+
timestamp=payload.timestamp,
327329
)
328330

329331
def iter_history_events(

ggshield/verticals/ai/agents/vscode.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ def parse_mcp_activity(
9191
model="",
9292
cwd=payload.raw.get("cwd", ""),
9393
input=payload.raw.get("tool_input", {}),
94+
timestamp=payload.timestamp,
9495
)
9596

9697
def _lookup_server_name(

ggshield/verticals/ai/hooks.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import hashlib
22
import json
33
import re
4+
from datetime import datetime, timezone
45
from typing import Any, Dict, List, Sequence, Set
56

67
from notifypy import Notify
@@ -75,6 +76,8 @@ def parse_hook_input(raw_content: str) -> list[HookPayload]:
7576
but in some cases files mentioned in the prompt will be read but the
7677
PreToolUse event will not be called. So we need to handle this case ourselves.
7778
"""
79+
timestamp = datetime.now(timezone.utc)
80+
7881
# Parse the content as JSON
7982
if not raw_content.strip():
8083
raise ValueError("Error: No input received on stdin")
@@ -103,7 +106,7 @@ def parse_hook_input(raw_content: str) -> list[HookPayload]:
103106
content = data.get("prompt", "")
104107
# Look for files mentioned in the prompt that could be read
105108
# without triggering a PRE_TOOL_USE event.
106-
payloads.extend(_parse_user_prompt(content, event_type, agent))
109+
payloads.extend(_parse_user_prompt(content, event_type, agent, timestamp))
107110

108111
elif event_type == EventType.PRE_TOOL_USE:
109112
tool = _parse_tool(data)
@@ -115,7 +118,7 @@ def parse_hook_input(raw_content: str) -> list[HookPayload]:
115118
content = tool_input.get("command", "")
116119
identifier = content
117120
# Try to detect a command that could be used to read a file.
118-
payloads.extend(_parse_command(content, event_type, agent))
121+
payloads.extend(_parse_command(content, event_type, agent, timestamp))
119122
elif tool == Tool.READ:
120123
# We only need to deal with the identifier, the content will be read by the Scannable
121124
identifier = lookup(tool_input, ["file_path", "filePath", "path"], "")
@@ -139,6 +142,7 @@ def parse_hook_input(raw_content: str) -> list[HookPayload]:
139142
identifier=identifier,
140143
agent=agent,
141144
raw=data,
145+
timestamp=timestamp,
142146
)
143147
)
144148

@@ -166,7 +170,7 @@ def _detect_agent(data: Dict[str, Any]) -> Agent:
166170

167171

168172
def _parse_user_prompt(
169-
content: str, event_type: EventType, agent: Agent
173+
content: str, event_type: EventType, agent: Agent, timestamp: datetime
170174
) -> List[HookPayload]:
171175
"""Parse the user prompt for additional payloads that we may miss."""
172176
payloads = []
@@ -183,13 +187,14 @@ def _parse_user_prompt(
183187
identifier=match,
184188
agent=agent,
185189
raw={},
190+
timestamp=timestamp,
186191
)
187192
)
188193
return payloads
189194

190195

191196
def _parse_command(
192-
content: str, event_type: EventType, agent: Agent
197+
content: str, event_type: EventType, agent: Agent, timestamp: datetime
193198
) -> List[HookPayload]:
194199
"""Parse the command for additional payloads that we may miss."""
195200
# In Windows, some agents (at least Codex) use the Get-Content command to read a file.
@@ -207,6 +212,7 @@ def _parse_command(
207212
identifier=identifier,
208213
agent=agent,
209214
raw={},
215+
timestamp=timestamp,
210216
)
211217
)
212218
return payloads

ggshield/verticals/ai/models.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import json
22
from abc import ABC, abstractmethod
33
from collections.abc import Iterable, Iterator
4-
from dataclasses import dataclass
4+
from dataclasses import dataclass, field
5+
from datetime import datetime, timezone
56
from enum import Enum, auto
67
from pathlib import Path
78
from typing import Any, Dict, List, Literal, Optional
@@ -67,6 +68,7 @@ class HookPayload:
6768
identifier: str
6869
agent: "Agent"
6970
raw: Dict[str, Any]
71+
timestamp: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
7072

7173
@property
7274
def scannable(self) -> Scannable:

0 commit comments

Comments
 (0)