Skip to content

feat(mcpserver): Slack URL 取得成功時は文書検索をスキップして referenced_slack_urls を優先 - #68

Merged
rluisr merged 4 commits into
mainfrom
feat/mcp-slack-url-direct-fetch
Jul 15, 2026
Merged

feat(mcpserver): Slack URL 取得成功時は文書検索をスキップして referenced_slack_urls を優先#68
rluisr merged 4 commits into
mainfrom
feat/mcp-slack-url-direct-fetch

Conversation

@rluisr

@rluisr rluisr commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Pull Request

Summary

When a Slack permalink in the MCP hybrid_search query resolves successfully, RAGent now short-circuits document hybrid search and returns the fetched message (including thread replies) in referenced_slack_urls first. This prevents MCP clients from answering from unrelated knowledge-base hits when the user clearly referenced a Slack conversation.

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update
  • Performance improvement
  • Refactoring (no functional changes)

Changes Made

  • Early-return path when Slack URL fetch yields messages: search_method=slack_url_direct_fetch, empty results, sources ["slack_urls"]
  • Include thread_replies on HybridSearchSlackResult (and OpenAPI) so thread context is available to MCP clients
  • Reorder HybridSearchResponse JSON so referenced_slack_urls appears before results
  • Update hybrid_search tool descriptions (JA/EN) to instruct clients to prioritize referenced_slack_urls
  • Extend unit tests to cover direct-fetch response shape, thread replies, and no OpenSearch calls

Motivation and Context

Previously, even after a Slack permalink was fetched, the tool continued hybrid document search and returned both document hits and the referenced Slack message. LLM clients often over-weighted document results and underused the explicitly referenced Slack conversation. Operators asking “この URL の内容を教えて” expect the linked message (and thread) to be the primary answer source.

This change makes successful Slack URL resolution the authoritative path: skip BM25/vector search, avoid unnecessary OpenSearch/Bedrock cost, and surface thread replies so answers can include the full conversation context.

How Has This Been Tested?

  • Unit tests (go test ./internal/mcpserver/)
  • Integration tests
  • Manual testing with local setup
  • Tested with AWS services (S3 Vectors, OpenSearch, Bedrock)

Test Configuration

  • Go version: local toolchain (module Go 1.23+)
  • AWS Region: N/A (unit tests only)
  • OpenSearch version (if applicable): N/A

Commands and Results

gofmt -l <changed go files>          # no output (clean)
go vet ./internal/mcpserver/         # pass
go test -v -count=1 ./internal/mcpserver/ -timeout 120s
  # PASS (includes TestHybridSearchToolFetchesReferencedSlackURLWithoutSlackSearchService
  #       and related Slack URL fetch tests)

Impact Analysis

Components Affected

  • CLI commands (cmd/)
  • Vectorization (internal/vectorizer/)
  • OpenSearch integration (internal/opensearch/)
  • S3 Vector operations (internal/s3vector/)
  • Slack bot (internal/slackbot/)
  • Bedrock embedding (internal/embedding/)
  • Configuration (internal/config/)
  • MCP server (internal/mcpserver/) — hybrid_search tool behavior and response schema
  • OpenAPI (openapi.yaml) — Slack result schema

AWS Resources Impact

  • No AWS resource changes
  • S3 bucket operations
  • OpenSearch index structure
  • IAM permissions required
  • Bedrock model usage

Breaking Changes

  • None
  • Yes (describe below)

Migration Guide

Behavioral change for MCP hybrid_search when a Slack permalink resolves:

  1. Document hybrid search is skipped (no OpenSearch/BM25 or vector query).
  2. Response uses search_method: "slack_url_direct_fetch", results: [], and populated referenced_slack_urls.
  3. HybridSearchSlackResult may include optional thread_replies.

Clients that assumed hybrid document results always accompany URL fetch should read referenced_slack_urls first (tool description now states this). Failed URL fetch still falls through to normal hybrid search.

Dependencies

  • No new dependencies
  • Dependencies added/updated (list below)

Documentation

  • README.md updated
  • CLAUDE.md updated
  • Inline code comments added/updated
  • API documentation updated (openapi.yaml)
  • Configuration examples updated

Checklist

  • My code follows the project's style guidelines (go fmt / gofmt and go vet)
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings or errors
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • Any dependent changes have been merged and published
  • I have checked my code for any security issues or exposed secrets
  • I have tested with the minimum supported Go version (1.23)
  • I have run go mod tidy to clean up dependencies (no module changes)

Performance Considerations

  • No performance impact
  • Performance improved (describe metrics)
  • Performance degraded but acceptable (explain trade-offs)

Successful Slack URL resolution skips OpenSearch health check, embedding, BM25, and vector search for that request.

File-by-File Changes

File Change Purpose
internal/mcpserver/hybrid_search_tool.go Add direct-fetch early return, convertSlackURLDirectResponse, thread reply mapping, tool description update Prioritize resolved Slack URLs and avoid document search noise/cost
internal/mcpserver/types.go Move referenced_slack_urls before results; add ThreadReplies on HybridSearchSlackResult JSON field order for client priority; expose thread context
internal/mcpserver/command.go Update hybrid_search tool description (JA/EN) Instruct MCP clients to prioritize referenced_slack_urls
internal/mcpserver/hybrid_search_tool_slack_url_test.go End-to-end HandleToolCall assertions for direct fetch, threads, no OS calls Guard the new behavior
openapi.yaml Document thread_replies on Slack result schema Keep API schema aligned
PULL_REQUEST.md PR description for review Reviewer context

Additional Notes

Screenshots/Logs

N/A


プルリクエスト(日本語版)

概要

MCP hybrid_search でクエリ内の Slack permalink が取得できた場合、文書ハイブリッド検索を行わず referenced_slack_urls(スレッド返信含む)を最優先で返すようにした。ユーザーが明示的に参照した会話を、無関係なドキュメントヒットより優先させる。

変更の種類

  • バグ修正(既存機能を破壊しない問題の修正)
  • 新機能(既存機能を破壊しない機能の追加)
  • 破壊的変更(既存機能の動作に影響を与える修正や機能)
  • ドキュメント更新
  • パフォーマンス改善
  • リファクタリング(機能的変更なし)

実装された変更

  • Slack URL 取得成功時の早期 return(search_method=slack_url_direct_fetch
  • thread_replies を hybrid_search の Slack 結果に追加
  • レスポンス JSON で referenced_slack_urlsresults より前に配置
  • ツール説明文を更新し、クライアントへ優先利用を指示
  • ユニットテストで直接取得パス・スレッド・OpenSearch 非呼び出しを検証

動機と背景

Slack URL を取得できても従来は文書検索も並行して走り、LLM がドキュメント結果を優先してしまうことがあった。「この URL の内容を教えて」系の問い合わせでは、参照メッセージとスレッドを一次情報にすべき。関連 Issue なし。

変更される挙動

条件 変更前 変更後
Slack permalink 取得成功 文書 hybrid 検索も実行し、両方を返す 文書検索をスキップし referenced_slack_urls のみ返却
取得失敗 / URL なし 従来どおり hybrid 検索 変更なし
Slack 結果のスレッド hybrid_search 側では返信が欠落しがち thread_replies に返信を含める

テスト方法

  • ユニットテスト(go test -v -count=1 ./internal/mcpserver/ -timeout 120s → PASS)
  • gofmt / go vet ./internal/mcpserver/ → PASS
  • 統合テスト
  • ローカル環境での手動テスト
  • AWSサービスでのテスト

テスト設定

  • Goバージョン: ローカル toolchain(module 1.23+)
  • AWSリージョン: N/A
  • OpenSearchバージョン: N/A

影響分析

影響を受けるコンポーネント

  • MCP server(internal/mcpserver/
  • OpenAPI(openapi.yaml

AWSリソースへの影響

  • AWSリソースの変更なし

破壊的変更

  • なし
  • あり(以下に記述)

Slack permalink 取得成功時は文書検索結果が空になる。referenced_slack_urls を読むクライアント実装が必要(ツール説明でも指示済み)。取得失敗時は従来パスにフォールバック。

依存関係

  • 新しい依存関係なし

ドキュメント

  • APIドキュメント更新(openapi.yaml

チェックリスト

  • コードがプロジェクトのスタイルガイドラインに従っている(gofmtgo vet
  • 自分のコードをセルフレビューした
  • 変更によって新しい警告やエラーが生成されない
  • 修正が効果的であることまたは機能が動作することを証明するテストを追加した
  • 新しいテストと既存のユニットテストがローカルで成功する
  • セキュリティ問題や露出した秘密情報がないかコードをチェックした

パフォーマンスに関する考慮事項

  • パフォーマンス改善: Slack URL 直接取得成功時は OpenSearch / 埋め込みを実行しない

懸念

  • URL が解決できた場合に文書コンテキストが一切付かないため、「URL + 関連ドキュメントも欲しい」ケースでは不足しうる。必要なら後続で opt-in フラグ検討。
  • JSON フィールド順は Go の struct タグ順に依存。クライアントがフィールド順に依存しない実装なら問題なし(テストで順序も確認済み)。

追加ノート

rluisr added 2 commits July 15, 2026 22:25
…earch

When a Slack permalink resolves, skip hybrid document search and return
referenced_slack_urls with thread replies first so MCP clients prioritize
the referenced conversation over unrelated document hits.
@rluisr

rluisr commented Jul 15, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

@github-actions

github-actions Bot commented Jul 15, 2026

Copy link
Copy Markdown

VSA Validation

OK。

  • 変更は主に internal/mcpserver スライス内に閉じており、スライス間の不正な直接依存は見当たりません。
  • internal/pkg/slacksearchmcpserver だけでなく slackbot / query など複数スライスから使われている共有コードなので、ここでの改善は共有層として許容範囲です。
  • openapi.yamlPULL_REQUEST.md は付随ドキュメントで、VSA の依存方向を崩していません。
  • git status --short は空で、未コミット変更はありません。

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: de2c6dd4de

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +247 to +257
if len(slackURLMessages) > 0 {
mcpResponse := convertSlackURLDirectResponse(searchRequest, slackURLMessages)
responseJSON, marshalErr := json.MarshalIndent(mcpResponse, "", " ")
if marshalErr != nil {
errorMsg := fmt.Sprintf("Failed to serialize response: %v", marshalErr)
return CreateToolCallErrorResult(errorMsg), marshalErr
}

hsta.logger.Printf("Slack URL direct fetch completed successfully - found %d results", mcpResponse.Total)
sendProgress(1.0, 1.0, "Slack URL direct fetch completed")
return CreateToolCallResult(string(responseJSON)), nil

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve eval exports on Slack URL direct fetches

When the MCP server runs with --export-eval and a Slack permalink resolves, this early-return path exits before the existing hsta.evalWriter.WriteRecord block later in HandleToolCallWithProgress. The same permalink requests used to fall through to hybrid search and be written to the eval JSONL, so direct-fetch traffic now disappears from evaluation exports; write an eval record before returning here.

Useful? React with 👍 / 👎.

Comment on lines +247 to +248
if len(slackURLMessages) > 0 {
mcpResponse := convertSlackURLDirectResponse(searchRequest, slackURLMessages)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Honor enabled Slack search on direct URL fetches

When a caller sets enable_slack_search (or uses an explicit Slack-search directive) and the permalink fetch succeeds, this branch returns before the later slackService.Search(...) block runs. In a request like “Slack検索を利用して、このURLに関連する会話も探して ”, the tool now returns only the referenced message and silently ignores the requested workspace Slack search; only short-circuit document search, or gate this return on Slack search not being requested.

Useful? React with 👍 / 👎.

Comment on lines +247 to +248
if len(slackURLMessages) > 0 {
mcpResponse := convertSlackURLDirectResponse(searchRequest, slackURLMessages)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Avoid treating partial Slack URL fetches as complete

When a query contains multiple Slack permalinks and FetchByURLs resolves at least one but returns errors for others, fetchReferencedSlackMessages drops those errors and this len(slackURLMessages) > 0 branch returns only the fetched subset with no warning or fallback to the normal URL/document search path. Users asking about several linked messages can get an apparently complete response that silently omits inaccessible or failed URLs; only short-circuit when all detected URLs were fetched or surface the partial failure.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b76286a0cf

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

f.logger.Printf("MessageFetcher: failed to fetch thread replies: %v", err)
} else {
enriched.ThreadMessages = threadMessages
return enriched, fmt.Errorf("failed to fetch thread replies: %w", err)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Do not fail direct URL fetches when replies fail

When a threaded Slack permalink's primary message is fetched but fetchThreadReplies fails, this returns a partial enriched message together with an error. In the MCP direct-fetch path, fetchReferencedSlackMessages treats any response.Errors as incomplete, so a URL-only request falls through to OpenSearch instead of returning the resolved primary message; if OpenSearch is unavailable, the user gets a search error even though the linked Slack message was fetched. Treat reply-context failures as warnings for the direct URL fetch, or otherwise count the primary URL as fetched.

Useful? React with 👍 / 👎.

}
allSlackURLsFetched := slackURLFetchComplete && len(slackURLMessages) > 0 && len(slackURLMessages) == len(slackURLs)
workspaceSlackSearchRequested := searchRequest.EnableSlackSearch && hsta.slackService != nil
if allSlackURLsFetched && !workspaceSlackSearchRequested {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve MCP tool enrichment on direct Slack fetches

When mcpClient is configured, a query that contains a resolvable Slack permalink and another MCP-backed reference now returns here before the later QueryWithRetry and attachMCPResultsToHybridResponse path runs. In that scenario, with workspace Slack search not requested, the response silently omits mcp_results even though the server wires MCP enrichment into the hybrid adapter; keep the direct Slack response, but still run/attach MCP tool results before returning.

Useful? React with 👍 / 👎.

@rluisr
rluisr deployed to e2e-test July 15, 2026 14:40 — with GitHub Actions Active
@rluisr
rluisr merged commit 9bc8ba2 into main Jul 15, 2026
17 checks passed
@rluisr
rluisr deleted the feat/mcp-slack-url-direct-fetch branch July 15, 2026 15:02
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