Skip to content

Feat/mcp scope per project#201

Open
duhd-vnpay wants to merge 34 commits intonextlevelbuilder:mainfrom
duhd-vnpay:feat/mcp-scope-per-project
Open

Feat/mcp scope per project#201
duhd-vnpay wants to merge 34 commits intonextlevelbuilder:mainfrom
duhd-vnpay:feat/mcp-scope-per-project

Conversation

@duhd-vnpay
Copy link

🔄 Hướng dẫn sử dụng tính năng MCP Scope Per Project

📌 Vấn đề giải quyết
Cùng 1 agent (ví dụ SDLC Assistant) nhưng phục vụ nhiều dự án khác nhau, mỗi dự án cần kết nối tới MCP server riêng. Ví dụ:
• Project xPOS → Jira board A, GitLab repo X
• Project Loyalty → Jira board B, GitLab repo Y

🧑‍💻 User Flow

Bước 1: Tạo Project (qua UI hoặc API)

Qua Web UI:

  1. Vào Admin Dashboard → sidebar mục "Connectivity" → "Projects"
  2. Bấm "New Project"
  3. Điền thông tin:
    • Name: xPOS (slug tự sinh: xpos)
    • Channel Type: telegram / slack / google-chat
    • Chat ID: ID của group chat gắn với project
    • Team (optional): gán team quản lý
    • Description (optional)
  4. Bấm Create

Qua API:
curl -X POST /v1/projects
-H "Authorization: Bearer "
-d '{"name":"xPOS","slug":"xpos","channel_type":"telegram","chat_id":"-100123456"}'

Bước 2: Cấu hình MCP Overrides cho Project

Qua Web UI:

  1. Trong danh sách Projects → bấm icon ⚙️ MCP Overrides của project
  2. Chọn MCP Server cần override (vd: jira, gitlab)
  3. Thêm Environment Variables override:
    • JIRA_PROJECT_KEY = XPOS
    • JIRA_BOARD_ID = 42
    • GITLAB_PROJECT_PATH = acb/xpos
  4. Bấm Save

Qua API:
curl -X PUT /v1/projects/{project_id}/mcp/jira
-H "Authorization: Bearer "
-d '{"env_overrides":{"JIRA_PROJECT_KEY":"XPOS","JIRA_BOARD_ID":"42"}}'

⚠️ Lưu ý bảo mật: Không thể đặt tên biến chứa TOKEN, SECRET, PASSWORD, API_KEY — hệ thống sẽ reject. Credentials phải cấu hình ở MCP server level, không phải project level.

Bước 3: Sử dụng — Hoàn toàn tự động! 🎉

Đây là phần hay nhất — user không cần làm gì thêm:

  1. User gửi tin nhắn trong Telegram group đã gắn với project
  2. Hệ thống tự động:
    • Nhận (channel_type, chat_id) → lookup project
    • Load MCP overrides của project đó
    • Tạo MCP process riêng cho project (isolated pool)
    • Merge env overrides vào MCP server config
  3. Agent tự động dùng đúng Jira board, đúng GitLab repo của project đó
  4. Khi agent delegate cho sub-agent → project context tự propagate qua delegation chain

Telegram Group "xPOS Dev" (chat_id: -100123456)
└→ User: "Tạo Jira ticket cho bug login"
└→ System: resolve project = xPOS
└→ MCP Jira khởi động với JIRA_PROJECT_KEY=XPOS
└→ Ticket tạo đúng board xPOS ✅

📊 Tóm tắt

Bước Ai làm Tần suất
1. Tạo Project Admin/PO 1 lần
2. Cấu hình MCP Overrides Admin/DevOps 1 lần (sửa khi cần)
3. Sử dụng hàng ngày Mọi user trong group Tự động, không cần thao tác
[15/03/2026 12:25 SA] Tiểu Hồ: 💡 Key Points
• Mapping 1:1 — mỗi group chat gắn với 1 project
• Isolation hoàn toàn — project A không ảnh hưởng project B (process riêng)
• Backward compatible — chat không gắn project nào → hoạt động như cũ
• Thay đổi config → có hiệu lực sau tối đa 10 phút (do Router cache TTL — đây là warning W6 trong review)

duhd-vnpay and others added 30 commits March 10, 2026 15:44
…ing, photo handling

- Fix zaloBotInfo to use account_name/display_name (not name)
- Add Label() method for bot display name resolution
- Handle 3 response formats in getUpdates: array, single object, wrapped
- Add photo_url field to zaloMessage for Zalo CDN image URLs
- Add display_name/is_bot to zaloFrom, chat_type to zaloChat
- Use PhotoURL with fallback to Photo in handleImageMessage

Co-Authored-By: Claude Opus 4.6 <[email protected]>
…ssing

Zalo CDN URLs are auth-restricted and expire quickly, causing read_image
tool failures. Now downloads photos to temp files (like Telegram channel)
so the agent pipeline can base64-encode and process them normally.

Falls back to passing the URL directly if download fails.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
… instance updates

When encryption key is empty, credentials stayed as map[string]any from
JSON unmarshal, causing pgx driver to fail encoding into bytea. Now
credentials are always marshaled to []byte regardless of encryption.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Add Party Mode to GoClaw: structured multi-persona AI discussions with
Standard (single LLM call), Deep (parallel thinking + cross-talk), and
Token-Ring (sequential turns) modes.

Backend: PartyStore + PG implementation, party engine with parallel
goroutines, 7 RPC methods (party.start/round/question/add_context/
summary/exit/list), 10 WebSocket events, migration 000014.

Frontend: React dashboard page with session list, chat view, persona
sidebar, mode controls, start dialog with 6 team presets, i18n (en/vi/zh).

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Previously, sanitizeHistory() only cleaned the in-memory copy for each
LLM request but never persisted the fix — causing the same "dropping
orphaned tool message" WARN to repeat on every single request forever.

Changes:
- sanitizeHistory() now returns drop count alongside cleaned messages
- When orphans are detected, cleaned history is persisted back to the
  session store via new SetHistory() method, then saved to DB
- Per-message WARN logs downgraded to DEBUG (cleanup is logged once
  at INFO level with total count)
- Added SetHistory() to SessionStore interface + both implementations

Co-Authored-By: Claude Opus 4.6 <[email protected]>
When a delegated agent (e.g. ui-ux-design-agent) spawns subagents, the
announce session key uses the format delegate:{uuid8}:{agentKey}:{id}.
The scheduler's RunFunc only handled agent:{agentId}:{rest} format,
falling back to the hardcoded "default" agent — which doesn't exist in
managed-mode deployments where the default agent has a custom key.

Add delegate: prefix parsing to extract the target agent key from
position 2 of the session key parts.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
- Add sender_id and channel to team task metadata for audit trail
- Remove assistant prefill in team task reminder (thinking models reject it)
- Add unit tests for team access control and sender_id tracking

Co-Authored-By: Claude Opus 4.6 <[email protected]>
…, mobile UX, budget, traces)

Resolved 13 file conflicts:
- zalo.go: kept local struct tags + upstream's n==0 safety check
- channel_instances.go: used upstream's credential merging (supersedes local bytea fix)
- factory.go, stores.go: merged both Party + Contacts/Activity/Snapshots stores
- loop_history.go: kept upstream skill inlining constants + local session persistence
- session_store.go, sessions/manager.go, sessions_ops.go: deduplicated SetHistory()
- sidebar.tsx, routes.tsx: added Party to upstream's restructured sidebar/routes
- protocol.ts, i18n/index.ts, sidebar.json (×3): merged Party + upstream events/translations
- version.go: bumped to schema version 18

Migration collision fix: renamed 000014_party_sessions → 000018_party_sessions

Co-Authored-By: Claude Opus 4.6 <[email protected]>
- Add nullish coalescing for PERSONA_COLORS array index
- Remove unused destructured `round` variable from PartyPage

Co-Authored-By: Claude Opus 4.6 <[email protected]>
…otocol

Backend:
- gateway_providers: read default_model from provider settings JSONB
- party.go: getEngine() prefers providers with DefaultModel, alphabetical
  fallback for determinism (fixes random Go map iteration)
- party.go: add slog.Error for round failures (was silently swallowed)

Frontend:
- use-party.ts: align all RPC params/events with snake_case wire format,
  add transformSession/mapStatus/selectSession helpers
- party-start-dialog.tsx: use actual DB persona keys (morpheus-persona etc.)
- party-page.tsx: use selectSession() for proper state hydration
- connection-status.tsx: fix status text alignment

Co-Authored-By: Claude Opus 4.6 <[email protected]>
transformSession() was dropping history/summary fields from backend
response, and selectSession() reset messages to empty array. Old
sessions appeared blank when clicked in sidebar.

- transformSession: preserve _history and _summary from backend
- hydrateMessages: new helper to convert RoundResult[] + SummaryResult
  into PartyMessage[] (round headers, persona messages, summary)
- selectSession: call hydrateMessages() instead of setMessages([])

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Root cause: `accumulators` map iterated with sequential `for i := 0; i < len(map)`
but SSE tool_call indices can be non-contiguous (e.g. {0, 2}), causing nil dereference
on `accumulators[i]` when key `i` doesn't exist.

Fixes:
1. openai.go: iterate map by sorted keys instead of sequential 0..len-1
2. lanes.go: add defer recover() in scheduler goroutine to prevent panics from
   crashing the entire process — logs error and returns semaphore token
3. tracing: add SweepOrphanTraces() to mark stuck running traces as error on
   gateway startup (running > 1h = orphan from previous crash)

Test results (18 tests):
- providers: 9/9 PASS (contiguous, non-contiguous, large gap, high index,
  thought_signature, text-only, empty, HTTP error, cancelled context)
- scheduler: 5/5 PASS (no panic, panic recovery, multiple panics, cancelled
  context, stats after panic)
- store/pg: 4/4 PASS (sweeps old running, ignores recent, ignores completed,
  no orphans)

Co-Authored-By: Claude Opus 4.6 <[email protected]>
… loops

When LLM hits max_tokens, tool call arguments may be incomplete/malformed JSON.
- Add truncation guard: skip tool execution when finish_reason=length, ask LLM to retry smaller
- Wire per-agent max_tokens from other_config JSONB (default 8192)
- Log warning on JSON parse failure for non-empty tool call arguments (truncation indicator)

Co-Authored-By: Claude Opus 4.6 <[email protected]>
# Conflicts:
#	internal/tools/team_tasks_tool.go
Previously, when an SSE stream was prematurely closed (e.g. proxy timeout,
network interruption), both Anthropic and OpenAI providers would return a
response with finish_reason="stop" and missing usage data — silently
delivering truncated content. The agent loop only checked for
finish_reason="length", so these cases went undetected.

Changes:
- Anthropic provider: track message_stop event; if absent, set
  finish_reason="interrupted" and add scanner.Err() check
- OpenAI provider: track [DONE] marker; if absent, set
  finish_reason="interrupted"
- Agent loop: on "interrupted" finish_reason, discard partial response
  and retry once (with retry event emitted to channels)

Co-Authored-By: Claude Opus 4.6 <[email protected]>
In Docker deployments, ~/.goclaw resolves to /app/.goclaw/ which is empty.
The actual workspace files live at GOCLAW_WORKSPACE (/app/workspace/).
This caused the Storage UI to show no files.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Download media URLs, verify local files, and detect MIME types for
WhatsApp incoming messages. Replaces raw string paths with resolved
media info including proper logging.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Migration file was renumbered to avoid conflict with upstream.
RequiredSchemaVersion bumped from 18 to 19.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Create project entity for per-group MCP env overrides with auto-updating
timestamps, FK to agent_teams, and unique constraints on slug and
(channel_type, chat_id). Bump RequiredSchemaVersion to 20.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
…erride types

Define store.ProjectStore interface (CRUD + MCP override methods),
Project struct embedding BaseModel, and ProjectMCPOverride struct.
Wire Projects field into Stores container.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
PostgreSQL-backed ProjectStore: full CRUD, chat-ID lookup, MCP override
upsert/delete/list with secret-pattern rejection. Register projects and
project_mcp_overrides in tablesWithUpdatedAt, wire into NewPGStores factory.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Add ProjectID and ProjectOverrides fields to RunRequest and
DelegateRunRequest for per-project MCP env overrides. Introduce
composite poolKey (name:projectID) in Pool.Acquire/Release so
different projects get separate MCP stdio processes with merged
environment variables. Update LoadForAgent, connectViaPool, and
filterTools to thread project context through the entire chain.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
… project)

Extend ResolverFunc signature to accept ResolveOpts (ProjectID, ProjectOverrides).
Add Router.GetForProject() which uses composite cache key "agentID:projectID"
to prevent cross-project MCP contamination in shared agents like sdlc-assistant.
The resolver closure now passes opts.ProjectID and opts.ProjectOverrides through
to mcpMgr.LoadForAgent(), connecting the per-project MCP scoping pipeline.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
…n and announce

- Create resolveProjectOverrides() helper in cmd/gateway_consumer_project.go
- Add ProjectID/ProjectOverrides tool context keys for delegation chain propagation
- Set project context in agent loop from RunRequest fields
- Add ProjectID/ProjectOverrides to DelegationTask struct
- Wire project context through delegate_prep.go buildRunRequest
- Use GetForProject() in consumer main lane, process lane, and runAgentFn
- Propagate project resolution through all 4 announce lanes
- Pass pgStores.Projects to consumeInboundMessages
Implements Google Chat as a new channel type (google_chat) in GoClaw:
- Service Account JWT auth with auto-refresh (no Google client libs)
- Pub/Sub REST pull loop for inbound messages with dedup cache
- Markdown → Google Chat text format conversion (bold, italic, links)
- Cards V2 for tables/structured content
- Byte-aware chunking for Vietnamese/CJK (3900-byte safety limit)
- Drive upload for long-form responses with retention cleanup
- Thread routing for group conversations
- Placeholder edit pattern (Thinking... → final response)
- Exponential backoff retry on 429/5xx
- 28 unit tests, all passing

Co-Authored-By: Claude Opus 4.6 <[email protected]>
duhd-vnpay and others added 4 commits March 14, 2026 21:36
Add ProjectHandler with 9 endpoints:
- GET/POST /v1/projects (list, create)
- GET /v1/projects/by-chat (lookup by channel_type + chat_id)
- GET/PUT/DELETE /v1/projects/{id} (get, update, delete)
- GET /v1/projects/{id}/mcp (list MCP overrides)
- PUT/DELETE /v1/projects/{id}/mcp/{serverName} (set/remove override)

Wire into gateway server and wireHTTP factory following existing
MCP handler pattern with bearer token auth and i18n error responses.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
- mergeEnv: priority, base immutability, nil handling
- secretKeyPattern: reject TOKEN/SECRET/PASSWORD/API_KEY
- resolveProjectOverrides: stub store, edge cases, graceful degradation
- pool key: composite key isolation between projects
- Router GetForProject: cache separation, empty project fallback

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Prevents false positives on keys like TOKENIZER_TYPE where TOKEN
appears as a substring but not as a discrete word.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
- Projects list with search, pagination, CRUD dialogs
- MCP env overrides dialog per project (add/remove)
- i18n support (en/vi/zh)
- Sidebar nav item under Connectivity group

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant