Skip to content

Commit 39d48e9

Browse files
fix(mcp): persist cwd for implicit sessions
1 parent be91849 commit 39d48e9

2 files changed

Lines changed: 75 additions & 15 deletions

File tree

internal/mcp/mcp.go

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,18 @@ var loadMCPStats = func(s *store.Store) (*store.Stats, error) {
5555
return s.Stats()
5656
}
5757

58+
func currentWorkingDirectory() string {
59+
cwd, err := os.Getwd()
60+
if err != nil {
61+
return ""
62+
}
63+
return cwd
64+
}
65+
66+
func ensureImplicitSessionWithCWD(s *store.Store, sessionID, project string) error {
67+
return s.CreateSession(sessionID, project, currentWorkingDirectory())
68+
}
69+
5870
// ─── Tool Profiles ───────────────────────────────────────────────────────────
5971
//
6072
// "agent" — tools AI agents use during coding sessions:
@@ -925,8 +937,8 @@ func handleSave(s *store.Store, cfg MCPConfig, activity *SessionActivity) server
925937
}
926938
}
927939

928-
// Ensure the session exists
929-
s.CreateSession(sessionID, project, "")
940+
// Ensure the implicit MCP session exists with the current working directory.
941+
_ = ensureImplicitSessionWithCWD(s, sessionID, project)
930942

931943
truncated := len(content) > s.MaxObservationLength()
932944

@@ -1135,8 +1147,8 @@ func handleSavePrompt(s *store.Store, cfg MCPConfig) server.ToolHandlerFunc {
11351147
sessionID = defaultSessionID(project)
11361148
}
11371149

1138-
// Ensure the session exists
1139-
s.CreateSession(sessionID, project, "")
1150+
// Ensure the implicit MCP session exists with the current working directory.
1151+
_ = ensureImplicitSessionWithCWD(s, sessionID, project)
11401152

11411153
_, err = s.AddPrompt(store.AddPromptParams{
11421154
SessionID: sessionID,
@@ -1376,8 +1388,8 @@ func handleSessionSummary(s *store.Store, cfg MCPConfig, activity *SessionActivi
13761388
sessionID = defaultSessionID(project)
13771389
}
13781390

1379-
// Ensure the session exists
1380-
s.CreateSession(sessionID, project, "")
1391+
// Ensure the implicit MCP session exists with the current working directory.
1392+
_ = ensureImplicitSessionWithCWD(s, sessionID, project)
13811393

13821394
_, err = s.AddObservation(store.AddObservationParams{
13831395
SessionID: sessionID,
@@ -1481,7 +1493,7 @@ func handleCapturePassive(s *store.Store, cfg MCPConfig, activity *SessionActivi
14811493

14821494
if sessionID == "" {
14831495
sessionID = defaultSessionID(project)
1484-
_ = s.CreateSession(sessionID, project, "")
1496+
_ = ensureImplicitSessionWithCWD(s, sessionID, project)
14851497
}
14861498

14871499
if source == "" {

internal/mcp/mcp_test.go

Lines changed: 56 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,31 @@ func callResultText(t *testing.T, res *mcppkg.CallToolResult) string {
4646
return text.Text
4747
}
4848

49+
func assertSessionSyncMutationDirectory(t *testing.T, s *store.Store, sessionID, wantDirectory string) {
50+
t.Helper()
51+
52+
mutations, err := s.ListPendingSyncMutations(store.DefaultSyncTargetKey, 100)
53+
if err != nil {
54+
t.Fatalf("list pending sync mutations: %v", err)
55+
}
56+
57+
for _, mutation := range mutations {
58+
if mutation.Entity != store.SyncEntitySession || mutation.EntityKey != sessionID || mutation.Op != store.SyncOpUpsert {
59+
continue
60+
}
61+
var payload map[string]any
62+
if err := json.Unmarshal([]byte(mutation.Payload), &payload); err != nil {
63+
t.Fatalf("decode session sync payload: %v", err)
64+
}
65+
if got := payload["directory"]; got != wantDirectory {
66+
t.Fatalf("expected session sync payload directory %q, got %#v in payload %s", wantDirectory, got, mutation.Payload)
67+
}
68+
return
69+
}
70+
71+
t.Fatalf("expected pending session upsert sync mutation for %q; got %#v", sessionID, mutations)
72+
}
73+
4974
func TestNewServerRegistersTools(t *testing.T) {
5075
s := newMCPTestStore(t)
5176
srv := NewServer(s)
@@ -1211,9 +1236,9 @@ func TestHandleSave_TopicKeyRevision_ReturnsCandidates(t *testing.T) {
12111236

12121237
// Save with topic_key (first write) — creates the topic.
12131238
req2 := mcppkg.CallToolRequest{Params: mcppkg.CallToolParams{Arguments: map[string]any{
1214-
"title": "Auth architecture sessions design updated",
1215-
"content": "Updated session-based auth design",
1216-
"type": "architecture",
1239+
"title": "Auth architecture sessions design updated",
1240+
"content": "Updated session-based auth design",
1241+
"type": "architecture",
12171242
"topic_key": "architecture/auth-sessions",
12181243
}}}
12191244
if _, err := h(context.Background(), req2); err != nil {
@@ -1222,9 +1247,9 @@ func TestHandleSave_TopicKeyRevision_ReturnsCandidates(t *testing.T) {
12221247

12231248
// Revise via same topic_key — this is the revision case.
12241249
req3 := mcppkg.CallToolRequest{Params: mcppkg.CallToolParams{Arguments: map[string]any{
1225-
"title": "Auth architecture sessions design revised",
1226-
"content": "Revised session-based auth design for the service layer",
1227-
"type": "architecture",
1250+
"title": "Auth architecture sessions design revised",
1251+
"content": "Revised session-based auth design for the service layer",
1252+
"type": "architecture",
12281253
"topic_key": "architecture/auth-sessions",
12291254
}}}
12301255
res3, err := h(context.Background(), req3)
@@ -1772,6 +1797,9 @@ func TestHandleSaveCreatesProjectScopedSession(t *testing.T) {
17721797
t.Fatalf("git remote add: %v\n%s", err, out)
17731798
}
17741799
t.Chdir(dir)
1800+
if err := s.EnrollProject("scoped-session-project"); err != nil {
1801+
t.Fatalf("enroll project: %v", err)
1802+
}
17751803

17761804
req := mcppkg.CallToolRequest{Params: mcppkg.CallToolParams{Arguments: map[string]any{
17771805
"title": "Decision",
@@ -1791,6 +1819,10 @@ func TestHandleSaveCreatesProjectScopedSession(t *testing.T) {
17911819
if sess.Project != "scoped-session-project" {
17921820
t.Fatalf("expected project=scoped-session-project, got %q", sess.Project)
17931821
}
1822+
if sess.Directory != dir {
1823+
t.Fatalf("expected directory=%q, got %q", dir, sess.Directory)
1824+
}
1825+
assertSessionSyncMutationDirectory(t, s, "manual-save-scoped-session-project", dir)
17941826
}
17951827

17961828
func TestHandleSavePromptCreatesProjectScopedSession(t *testing.T) {
@@ -1806,6 +1838,9 @@ func TestHandleSavePromptCreatesProjectScopedSession(t *testing.T) {
18061838
t.Fatalf("git remote add: %v\n%s", err, out)
18071839
}
18081840
t.Chdir(dir)
1841+
if err := s.EnrollProject("prompt-project"); err != nil {
1842+
t.Fatalf("enroll project: %v", err)
1843+
}
18091844

18101845
req := mcppkg.CallToolRequest{Params: mcppkg.CallToolParams{Arguments: map[string]any{
18111846
"content": "How do I set up auth?",
@@ -1815,9 +1850,14 @@ func TestHandleSavePromptCreatesProjectScopedSession(t *testing.T) {
18151850
t.Fatalf("save prompt: err=%v isError=%v", err, res.IsError)
18161851
}
18171852

1818-
if _, err := s.GetSession("manual-save-prompt-project"); err != nil {
1853+
sess, err := s.GetSession("manual-save-prompt-project")
1854+
if err != nil {
18191855
t.Fatalf("expected session manual-save-prompt-project: %v", err)
18201856
}
1857+
if sess.Directory != dir {
1858+
t.Fatalf("expected directory=%q, got %q", dir, sess.Directory)
1859+
}
1860+
assertSessionSyncMutationDirectory(t, s, "manual-save-prompt-project", dir)
18211861
}
18221862

18231863
func TestHandleSessionSummaryCreatesProjectScopedSession(t *testing.T) {
@@ -1833,6 +1873,9 @@ func TestHandleSessionSummaryCreatesProjectScopedSession(t *testing.T) {
18331873
t.Chdir(dir)
18341874

18351875
s := newMCPTestStore(t)
1876+
if err := s.EnrollProject("summary-session-project"); err != nil {
1877+
t.Fatalf("enroll project: %v", err)
1878+
}
18361879
h := handleSessionSummary(s, MCPConfig{}, NewSessionActivity(10*time.Minute))
18371880

18381881
req := mcppkg.CallToolRequest{Params: mcppkg.CallToolParams{Arguments: map[string]any{
@@ -1843,9 +1886,14 @@ func TestHandleSessionSummaryCreatesProjectScopedSession(t *testing.T) {
18431886
t.Fatalf("session summary: err=%v isError=%v text=%s", err, res.IsError, callResultText(t, res))
18441887
}
18451888

1846-
if _, err := s.GetSession("manual-save-summary-session-project"); err != nil {
1889+
sess, err := s.GetSession("manual-save-summary-session-project")
1890+
if err != nil {
18471891
t.Fatalf("expected session manual-save-summary-session-project: %v", err)
18481892
}
1893+
if sess.Directory != dir {
1894+
t.Fatalf("expected directory=%q, got %q", dir, sess.Directory)
1895+
}
1896+
assertSessionSyncMutationDirectory(t, s, "manual-save-summary-session-project", dir)
18491897
}
18501898

18511899
func TestHandleCapturePassiveCreatesProjectScopedSession(t *testing.T) {

0 commit comments

Comments
 (0)