-
Notifications
You must be signed in to change notification settings - Fork 3k
feat(integrations): add retrieval-only external context search #7586
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
297a505
feat(integrations): add direct external context provider
doudouOUC 44e00de
fix(integrations): harden external context failure handling
doudouOUC aa70bbe
refactor(integrations): narrow external context to retrieval
doudouOUC cf84a9a
fix(integrations): harden external context deployment
doudouOUC 89d4660
fix(integrations): preserve external context results
doudouOUC 388be23
fix(integrations): honor provider proxy settings
doudouOUC de9d5f7
fix(integrations): diagnose invalid proxy settings
doudouOUC File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,195 @@ | ||
| # External Context extension | ||
|
|
||
| This private Qwen Code integration connects one interactive CLI process to one | ||
| administrator-bound external context corpus without changing Qwen Core. Phase | ||
| 1 exposes exactly one on-demand, retrieval-only MCP tool: | ||
| `context_search({ query })`. | ||
|
|
||
| The built-in adapters support Mem0 Platform V3 search and a small Generic HTTP | ||
| Search V1 contract for existing knowledge or RAG services. There are no hooks, | ||
| automatic recall, write tools, personal memory, trusted user identity, | ||
| per-document ACLs, or tamper-resistant audit in this phase. | ||
|
|
||
| Use the governed Gateway/Orchestrator Profile described in #7449 when those | ||
| controls are required. | ||
|
|
||
| ## Trust boundary | ||
|
|
||
| The model can provide only the search query. Provider type, endpoint, | ||
| credential, Mem0 `app_id`, and all other corpus selectors are fixed before the | ||
| MCP server starts. | ||
|
|
||
| The actual corpus-isolation boundary is the provider-side credential, project, | ||
| index, or corpus. A Mem0 `app_id` or any other client-supplied filter is | ||
| classification, not authorization. The credential must be restricted to the | ||
| intended corpus and should be read-only where the provider supports that. | ||
|
|
||
| The extension manifest alone is not a managed binding. Qwen merges MCP servers | ||
| by name, and a settings, project, or command-line server named | ||
| `external-context` can replace the manifest contribution while retaining the | ||
| same permission-rule name. A managed deployment must therefore start Qwen with | ||
| an administrator-owned `--mcp-config` based on | ||
| `examples/managed-mcp.json`. The Phase 1 launcher must construct the complete | ||
| Qwen argument vector itself and must not pass through arbitrary caller | ||
| arguments. This command-line tier overrides user, project, workspace, and | ||
| system MCP settings. The documented permission rule is safe to deploy only | ||
| inside that pinned process. | ||
|
|
||
| The launcher must also construct an administrator-approved environment rather | ||
| than inherit caller-controlled values. Qwen can subsequently load values from | ||
| the repository's `.env` and `.qwen/.env` files, so the Direct Profile requires | ||
| the repository, those files, and same-UID code to be trusted. The source pin | ||
| prevents same-name MCP configuration collisions; it is not a process sandbox. | ||
| Use the governed profile when those inputs may be hostile or when credentials | ||
| and process execution must be isolated. | ||
|
|
||
| The extension omits MCP `readOnlyHint` because a provider search may record | ||
| access metadata or otherwise have provider-side read effects. It exposes no | ||
| explicit mutation operation, but the Qwen process still passes search queries | ||
| to an external service, so this integration is not a DLP boundary. Credentials | ||
| inherited by Qwen may also be visible to same-UID processes and tools. Use the | ||
| governed profile when the credential or outbound-query policy must be isolated | ||
| from the CLI user. | ||
|
|
||
| The managed-settings example allows Qwen to invoke search without per-call | ||
| confirmation. It is on-demand rather than prompt-triggered, but it is not | ||
| necessarily initiated manually by the user. In interactive non-YOLO mode, | ||
| placing the tool under `permissions.ask` requests confirmation. YOLO mode | ||
| auto-approves ordinary tools despite `ask`, and users can change approval mode | ||
| during a session. Phase 1 does not provide non-bypassable per-call | ||
| confirmation; use the governed profile when that is required. | ||
|
|
||
| ## Configure | ||
|
|
||
| 1. Give the repository its own provider-side project, index, or corpus and a | ||
| credential restricted to it. Verify that the credential cannot access or | ||
| select another corpus. | ||
| 2. Copy the applicable provider configuration from `examples/` to an | ||
| administrator-owned location outside the repository that the CLI user | ||
| cannot modify. Configure `apiKeyEnv` or `tokenEnv` if needed, then set the | ||
| referenced environment variable to the credential. `timeoutMs` defaults to | ||
| 5000 and may be between 1 and 30000 milliseconds. | ||
| 3. Have the managed launcher set `QWEN_EXTERNAL_CONTEXT_CONFIG` to the absolute | ||
| configuration path. | ||
| 4. From the Qwen Code checkout, install dependencies and build this workspace: | ||
|
|
||
| ```bash | ||
| npm install | ||
| npm run build --workspace @qwen-code/external-context | ||
| ``` | ||
|
|
||
| Phase 1 is a private monorepo workspace. Copying the directory or its npm | ||
| tarball without packaging its runtime dependencies is not a supported | ||
| deployment. | ||
|
|
||
| 5. Copy `examples/managed-mcp.json` to an administrator-owned location and | ||
| replace every placeholder with an absolute path. The `command`, `args`, and | ||
| `cwd` must identify an administrator-controlled Node executable, reviewed | ||
| checkout, and dependency tree that the CLI user cannot modify. The managed | ||
| launcher must accept no arbitrary Qwen arguments. It must construct a clean, | ||
| administrator-approved environment, inject the provider configuration and | ||
| credential, change to the intended repository, and invoke: | ||
|
|
||
| ```bash | ||
| qwen --mcp-config /administrator/path/external-context-mcp.json | ||
| ``` | ||
|
|
||
| The MCP subprocess honors `HTTP_PROXY`, `HTTPS_PROXY`, and `NO_PROXY` | ||
| through an environment-aware dispatcher. If the provider requires an | ||
| egress proxy, the launcher must include the administrator-approved proxy | ||
| variables in that clean environment. Include `localhost`, `127.0.0.1`, and | ||
| `[::1]` in `NO_PROXY` when using the loopback Generic HTTP provider. | ||
|
|
||
| 6. Point `QWEN_CODE_SYSTEM_SETTINGS_PATH` at an administrator-controlled copy | ||
| of `examples/managed-settings.json` only inside this managed launcher; do not | ||
| install its automatic allow rule for unrelated Qwen sessions. It disables | ||
| `/cd` to reduce accidental workspace/corpus mismatch and allows the pinned | ||
| search tool. Neither setting is an authorization boundary: the provider | ||
| credential is still the corpus boundary, and a new Qwen process is required | ||
| to switch repositories. | ||
|
|
||
| For a local trusted trial, the built directory may instead be linked with | ||
| `qwen extensions link`. The extension manifest contribution and | ||
| workspace-scoped enablement are convenience mechanisms only; they do not | ||
| provide the managed MCP source binding described above. Before starting Qwen, | ||
| the trial environment must set `QWEN_EXTERNAL_CONTEXT_CONFIG` to the absolute | ||
| configuration path and set the credential environment variable referenced by | ||
| that file. | ||
|
|
||
| Each MCP subprocess reads configuration and credentials once when it starts. | ||
| Qwen may restart that subprocess, so the configuration path, file contents, | ||
| and credential-to-corpus binding must remain immutable for the whole Qwen | ||
| session. Do not overwrite or reuse a configuration path for another corpus. | ||
| Changing the working directory does not change the configured corpus. The | ||
| managed settings disable Qwen's `/cd` command as an accidental-misuse guard, | ||
| but cannot prevent every same-UID action. To switch corpora, terminate the old | ||
| Qwen session and start a new one with a new managed configuration path. | ||
|
|
||
| Phase 1 emits no local per-request audit record. It does not write queries, | ||
| results, credentials, provider errors, or operation metadata to `stderr`. | ||
| Sanitized startup configuration errors may be written once before the MCP | ||
| server exits; unexpected startup failures remain opaque. | ||
| Operators who need access records may use provider-side logs, but those are | ||
| outside this integration and are not a tamper-resistant compliance audit. | ||
|
|
||
| ## Generic HTTP Search V1 | ||
|
|
||
| The configured `baseUrl` must be an origin with no path, query, credentials, or | ||
| fragment. That origin receives a request at the fixed path | ||
| `/v1/context/search`: | ||
|
|
||
| ```http | ||
| POST /v1/context/search | ||
| Authorization: Bearer <credential> | ||
| Accept: application/json | ||
| Content-Type: application/json | ||
|
|
||
| {"query":"normalized query","limit":5} | ||
| ``` | ||
|
|
||
| The response is: | ||
|
|
||
| ```json | ||
| { | ||
| "items": [ | ||
| { | ||
| "id": "opaque-id", | ||
| "content": "retrieved text", | ||
| "title": "optional title", | ||
| "uri": "optional provenance URI", | ||
| "score": 0.82, | ||
| "updated_at": "2026-07-23T00:00:00Z" | ||
| } | ||
| ] | ||
| } | ||
| ``` | ||
|
|
||
| The fixed endpoint and the credential's effective capabilities must together | ||
| restrict access to one corpus. A bearer credential that can access another | ||
| corpus through another endpoint or selector does not meet the Direct Profile | ||
| boundary. The request contains no client-selected tenant, repository, | ||
| namespace, or filter. HTTPS is required except for explicit loopback HTTP used | ||
| in local development. | ||
|
|
||
| ## Mem0 Platform V3 | ||
|
|
||
| The adapter calls `POST /v3/memories/search/` with the configured `app_id`, | ||
| `top_k: 5`, `threshold: 0.1`, and `rerank: false`. The API key's effective | ||
| Mem0 Project must already be restricted to the intended corpus; a different | ||
| `app_id` in the same broadly accessible Project does not establish isolation. | ||
| This extension does not call Mem0 add, update, or delete APIs. | ||
|
|
||
| Mem0 Memory Decay is opt-in and off by default. If enabled, search reinforces | ||
| returned memories, updates access history, and can affect later ranking. Keep | ||
| it disabled when search must have no semantic provider-side state change. | ||
| Provider audit or access logs may still be retained. See | ||
| [Mem0 Memory Decay](https://docs.mem0.ai/platform/features/memory-decay). | ||
|
|
||
| ## Rollout and rollback | ||
|
|
||
| Enable the pinned managed MCP for one workspace, validate on-demand search | ||
| quality and provenance, and then expand to other independently configured | ||
| corpora. Removing the pinned configuration from the managed launcher rolls | ||
| back the Qwen integration; local trials can instead disable or remove the | ||
| extension. Phase 1 does not call explicit mutation or deletion APIs, but | ||
| rollback does not remove provider-side search logs or access metadata. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| { | ||
| "version": 1, | ||
| "timeoutMs": 5000, | ||
| "provider": { | ||
| "type": "generic-http-search-v1", | ||
| "baseUrl": "https://context.example.com", | ||
| "tokenEnv": "CONTEXT_API_TOKEN" | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| { | ||
| "mcpServers": { | ||
| "external-context": { | ||
| "command": "/absolute/path/to/node", | ||
| "args": [ | ||
| "/administrator/path/to/qwen-code/integrations/external-context/dist/main.js" | ||
| ], | ||
| "cwd": "/administrator/path/to/qwen-code/integrations/external-context", | ||
| "includeTools": ["context_search"] | ||
| } | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| { | ||
| "slashCommands": { | ||
| "disabled": ["cd"] | ||
| }, | ||
| "permissions": { | ||
| "allow": ["mcp__external-context__context_search"] | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| { | ||
| "version": 1, | ||
| "timeoutMs": 5000, | ||
| "provider": { | ||
| "type": "mem0-platform-v3", | ||
| "apiKeyEnv": "MEM0_API_KEY", | ||
| "appId": "repository-memory" | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| { | ||
| "name": "@qwen-code/external-context", | ||
| "version": "0.20.1", | ||
| "private": true, | ||
| "description": "Provider-bound external context search for Qwen Code", | ||
| "type": "module", | ||
| "engines": { | ||
| "node": ">=22.0.0" | ||
| }, | ||
| "scripts": { | ||
| "clean": "node -e \"const fs=require('node:fs'); fs.rmSync('dist',{recursive:true,force:true}); fs.rmSync('tsconfig.tsbuildinfo',{force:true})\"", | ||
| "build": "npm run clean && tsc --build", | ||
| "test": "vitest run --config vitest.config.ts", | ||
| "test:ci": "vitest run --config vitest.config.ts", | ||
| "typecheck": "tsc --noEmit", | ||
| "lint": "eslint src" | ||
| }, | ||
| "files": [ | ||
| "dist", | ||
| "examples", | ||
| "qwen-extension.json", | ||
| "README.md" | ||
| ], | ||
| "dependencies": { | ||
| "@modelcontextprotocol/sdk": "^1.25.2", | ||
| "undici": "^7.28.0", | ||
| "zod": "^3.25.0" | ||
| }, | ||
| "devDependencies": { | ||
| "@types/node": "^22.0.0", | ||
| "typescript": "^5.4.5", | ||
| "vitest": "^3.2.4" | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| { | ||
| "name": "external-context", | ||
| "displayName": { | ||
| "en": "External Context", | ||
| "zh": "外部上下文" | ||
| }, | ||
| "description": { | ||
| "en": "Provider-bound external context search for trusted teams", | ||
| "zh": "为可信团队提供 Provider 绑定的外部上下文搜索" | ||
| }, | ||
| "version": "1.0.0", | ||
| "mcpServers": { | ||
| "external-context": { | ||
| "command": "node", | ||
| "args": ["${extensionPath}${/}dist${/}main.js"], | ||
| "cwd": "${extensionPath}", | ||
| "includeTools": ["context_search"] | ||
| } | ||
| } | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.