Skip to content

[4/5] Expose the scoped announcement MCP runtime - #272

Merged
SophieS0ng merged 8 commits into
users/rebova/org-announcements-prereleasefrom
users/rebova/org-announcements-review-runtime
Sep 26, 2026
Merged

SophieS0ng merged 8 commits into
users/rebova/org-announcements-prereleasefrom
users/rebova/org-announcements-review-runtime

Conversation

@rebova-microsoft

@rebova-microsoft rebova-microsoft commented Sep 11, 2026 •

Copy link
Copy Markdown
Contributor

Description

Expose the separate announcements MCP provider, widget resource, manager/editor opener, audience lookup, and widget-owned authoring operations.

Current diff

  • Keep flat opener arguments: manager requires titleId; create requires explicit create intent without an existing bulletinId; edit requires bulletinId. Reject malformed combinations before generating widget retry state.
  • Open incomplete and repairable copies as ordinary ID-less create editors with config: null, retaining supplied content and localized copy titles. Opening never saves, publishes, transitions, or duplicates.
  • Preserve captured tenant/account directory leases, scoped retry context, and non-repeating recovery for indeterminate or committed-but-unrefreshed writes.
  • Preserve isError: true with operation-specific structured failure payloads. CommittedRefreshFailed remains non-retryable and retains the actual refresh cause without fabricating a saved ID, item, or manager result.
  • Restrict the trusted Vorpal widget origin to the known dev, DF, and production hosts. Loopback and *.devtunnels.ms origins require the explicit VORPAL_WIDGET_ALLOW_DEVELOPMENT_ORIGIN=1 development gate; arbitrary HTTPS and misleading suffix hosts remain rejected.
  • Share the public title/bulletin request validators between the client and server. Bulletin IDs reject URL dot-segments, route-shaping delimiters, encoded separators, control characters, whitespace changes, and excessive length before route construction. Every mutation validates a supplied ID before authentication and client acquisition.
  • Add cross-provider discovery/import-isolation coverage and retain existing privacy-safe feature telemetry. No additional announcements client-event bridge is introduced.

Feature contract

The opener and discovery tools are read-only. The widget owns mutations after opening. Editable copies use the create opener; their first explicit Save establishes identity. The app-only immediate-write duplicate_bulletin tool and persisted Unarchive transition remain unchanged. Unknown ownership and malformed retry requests are not accepted. Opening content for repair does not make its supplied actions valid to save or publish.

The paired Vorpal consumer catches McpToolError and returns error.result, preserving the structured MCP result for isError: true responses. This keeps the non-retryable CommittedRefreshFailed guidance available to the widget instead of reducing it to a generic thrown error.

Stack and dependency

This is slice 4/5. Head: users/rebova/org-announcements-review-runtime. Base: users/rebova/org-announcements-prerelease after #271 merged on September 25, 2026. The future release target remains TBD and its final promotion baseline must be confirmed separately.

Bootstrap prerequisite #262 merged into release/planner-landing-page on September 10, 2026. This stack remains pinned to cacb1bec056428809f1ccb0383561190d516bee4, which is an ancestor of merge commit 8a04f5f40f334e729a3497877edca655730f1be2. Unchanged prerequisite work is excluded from this slice.

Testing

  • Python 3.11 announcement/runtime suite: 641 passed.
  • MCP foundation and import-isolation suite: 258 passed, 1 skipped (expected Windows POSIX-permission skip).
  • Ruff, Python compilation, formatting, and git diff --check: passed.
  • Synthetic handler responses match the paired published frontend schemas; this does not establish live Graph or Weve behavior.

Readiness

  • Relevant offline tests pass.
  • Python 3.11 local verification.
  • Authorized live integration and end-to-end validation.

Paired hosted-widget and real-backend integration remain required before rollout. This PR performs no deployment and is not release approval.

@rebova-microsoft rebova-microsoft left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Walkthrough — Runtime file guide.

This informational walkthrough maps all eight changed files. Runtime here connects agent/widget requests to the authoring and directory clients from PRs #270–#271; it is not UI layout or backend storage. Toolkit startup and entry-point routing belong to PR #273.

Comment thread .github/workflows/ci.yml
tests/mcp/agentconfig_org_announcements/test_authoring_client.py
tests/mcp/agentconfig_org_announcements/test_drafts.py
tests/mcp/agentconfig_org_announcements/test_graph_directory.py
tests/mcp/agentconfig_org_announcements

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Walkthrough — Include the runtime checks in CI.

The announcements job now selects the whole feature test directory instead of naming three individual files. That includes the new protocol, client-lifecycle, and telemetry tests alongside the earlier client, draft, and directory tests, without adding a workflow entry for each file. The foundation job also gains test_import_isolation.py, which checks that the sibling MCP providers load their own modules. These changes expand test selection; they do not start or deploy the announcements service.

meta=_widget_tool_meta(),
annotations=_READ_ONLY_ANNOTATIONS,
)
async def open_org_announcements(

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Walkthrough — Connect requests to announcement services.

This file connects requests to PR #270's authoring client and PR #271's Graph client—not screen layout or storage.

  • org_announcements_widget returns a static HTML shell loading Vorpal. Metadata exposes read tools to the model/app and mutations only to the app.
  • open_org_announcements takes flat titleId/view arguments. Manager has no mode; editor requires create or edit, with an ID only for edit. Pydantic checks combinations before widget-error handling: malformed calls become ToolError; valid failed opens retain the original request without inventing an ID/mode. Opening never saves.
  • Each operation captures its authoring client and authenticated tenant alongside titleId; directory work also binds the account. get_graph_client counts active users so replacing a tenant/account client does not close it mid-operation.
  • save_bulletin sends complete content/audience. transition_bulletin delegates identifier/status-only changes; publishing uses a full save, not a republish mode. duplicate_bulletin strips identity/audit fields and creates a Draft before any later editor opening, preserving its title and leaving the source unchanged.
  • _saved_item_result combines acknowledged content with refreshed manager/audience data. IndeterminateWrite and CommittedRefreshFailed are non-retryable results: inspect current state instead of blindly repeating a create/copy. A manager refresh does not guarantee identifying the saved record.

return _ADK_TELEMETRY


def record_operation(

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Walkthrough — Measure operations without recording authored content.

record_operation adapts the runtime's calls to the existing adk.api.call emitter; it is not a separate widget-event bridge. The api_endpoint field holds an allowlisted tool name, not the service URL. Success adds outcome and duration; failure also adds a short identifier-shaped error code and a broad backend/Graph/MCP category, with an empty error message. Unknown operation/source values are replaced, and message-shaped or overlong codes become UnknownError. Error codes are shape-checked, not a fixed allowlist. The helper resolves the emitter lazily and tolerates its absence or emission exceptions, keeping those telemetry failures separate from the tool result.



@pytest.mark.parametrize("modules", [LANDING])
@pytest.mark.parametrize("modules", [LANDING, ANNOUNCEMENTS])

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Walkthrough — Share discovery without coupling the feature providers.

Adding announcements to the existing parameterized tests applies the same discovery contract to both clients: shared helper methods, preserved titleId values, expected request routes, rejected invalid searches, and errors for malformed collections rather than empty successes. The feature-tool check uses a discovery-only fake, so listing/searching agents does not require configuration initialization or attach a widget. Announcements awaits its client and advertises readOnlyHint; the landing-page provider keeps its existing synchronous accessor and unchanged metadata. Search uses a POST request here, but its purpose is still to find existing agents, not create configuration.

assert org_server._client is None


def test_the_authoring_client_is_constructed_off_the_event_loop(

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Walkthrough — Keep sign-in from blocking the request transport.

These tests cover how the server obtains and replaces its authoring client, rather than announcement content. Fakes check that construction happens off the async event-loop thread, concurrent first requests share one construction, and reset drops and closes the old client so a subsequent call can build another. Authoring-side 401 failures trigger that reset; Graph authentication failures and other tested service statuses do not. Mocked sign-in paths also check that notices go to stderr, not stdout, because stdout carries MCP protocol messages. Additional guards look for stdout prints and overly verbose HTTP logging. No real browser sign-in is needed for these scenarios.

return install


def _call(tool: str, arguments: dict[str, Any], *, include_scope: bool = True) -> Any:

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Walkthrough — Exercise the contract the host and widget call.

_call goes through FastMCP's tool dispatcher, while fake authoring/directory clients record requests and inject failures. This covers resource metadata, tool visibility, structured results, complete saves, minimal transitions, duplicate creation, and audience lookup. The schema assertions intentionally require only titleId and view globally; separate invocation tests check the conditional manager/create/edit rules and ToolError boundary. Valid failed opens retain retry context instead. Other cases distinguish uncertain writes from acknowledged writes whose refresh fails, checking that the mutation is not repeated. Scope and overlapping-request cases check captured tenant/agent/account context and Graph-client replacement. These are host/runtime contract checks with simulated dependencies, not evidence about rendered UI or deployed backend behavior.

Comment thread tests/mcp/agentconfig_org_announcements/test_telemetry_privacy.py
)


def test_both_servers_load_with_their_own_client_module() -> None:

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Walkthrough — Prevent sibling servers from importing each other's code.

Both MCP providers have files named client.py and server.py. Loading them under those plain module names in one Python process can make the second provider reuse the first provider's module. These tests check the shared isolated loader returns distinct clients from the correct directories, reuses each provider's own modules on repeated loads, and restores previous plain-name entries. A source scan discourages direct flat imports in test modules; a separate subprocess checks that both providers coexist in one interpreter. This protects the test harness from misleading cross-provider failures, not tenant isolation or live service routing.

@rebova-microsoft
rebova-microsoft added this pull request to stack #282 September 14, 2026 23:41
@rebova-microsoft

Copy link
Copy Markdown
Contributor Author

Commit 50ced4d documents repairable copy opening and adds regressions for zero-write hydration, priority and unresolved-audience handling, backend action rejections, and exactly-one-write committed-refresh failure packaging. The mutation handlers and failure envelope are unchanged; dependency updates were merged forward from the existing contracts/audience branches.

@rebova-microsoft
rebova-microsoft force-pushed the users/rebova/org-announcements-review-runtime branch from 50ced4d to ee64ba4 Compare September 18, 2026 22:20
@SophieS0ng
SophieS0ng force-pushed the users/rebova/org-announcements-review-runtime branch from ee64ba4 to f77eb74 Compare September 24, 2026 21:47
@daeunJe0ng

Dawn Jeong (daeunJe0ng) commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

[copilot-review] Re-verified at head d46e9c15. Verdict: Approve with should-fixes (none blocking). This supersedes my earlier review at f77eb747: the follow-up commit "Harden announcement runtime error handling" resolved the two model-visible leaks below, with tests. Only two authors have commented on this PR (the author's file walkthroughs and this review), so there is no external reviewer feedback to reconcile.

Design verdict

Right layer for a 4/5 runtime slice. Verified directly: open_org_announcements is read-only and model/app-visible; save_bulletin, transition_bulletin, and duplicate_bulletin are app-only via _app_only_tool_meta(); the model-visible opener rejects bad manager/editor argument combinations before any I/O; tenant and title scope come from the authoring client, never caller input; Graph $search and $filter are both injection-safe (escape_search_value plus _is_safe_filter_id); committed-but-unrefreshed writes are correctly non-retryable so an unkeyed create or duplicate cannot be replayed into a second record (_MUTATION_ERRORS includes both _FailureResult and GraphDirectoryError, so a Graph failure during post-commit refresh still yields the committed, non-retryable result rather than an unhandled error).

Resolved since the previous review (head d46e9c1)

  1. RESOLVED — model-visible backend detail leak. _open_error_payload now rebuilds model-visible failures from a local _MODEL_VISIBLE_BACKEND_MESSAGES map and collapses unknown backend codes to a generic InvalidRequest, so backend detail / str(error) no longer reaches the model. Covered by new tests (400/409/422 sanitization, recognized-code rebuild).
  2. RESOLVED — validation input echo. StrictModel now sets hide_input_in_errors=True, so save_bulletin (and every request model) no longer echoes the maker's title/description/action URL/group IDs on a ValidationError. Covered by new tests in test_drafts.py and test_mcp_app_protocol.py.

Still open (posted as inline comments)

  • Should-fix (defense in depth): _resolve_widget_origin accepts any HTTPS VORPAL_WIDGET_ORIGIN; restrict to an explicit allowlist. See inline on server.py:170.
  • Nit: private validators _validate_title_id / _validate_bulletin_id imported from client across the MCP request boundary; promote to a shared public module. See inline on server.py:52.
  • Question: confirm the real MCP app host preserves structuredContent on isError=True for CommittedRefreshFailed; if it drops it, the "saved, do not repeat" guidance is lost. See inline on server.py:558.

What is good

Content-free telemetry and logs (stable codes only, never content, query, group IDs, tokens, or claims); group IDs never rendered as display names; allowlisted MSAL error codes before logging; careful async locking so concurrent tool calls share one sign-in; follow_redirects=False on the Graph client; Graph error strings are static (no query text or group IDs), so the unsanitized Graph-source path to the model is safe; and meaningful tests across mutation visibility, committed-refresh failures, audience hydration and search, Graph auth and cache failures, import isolation, telemetry privacy, and the new sanitization paths.

Verification

Read server.py, drafts.py, graph_directory_client.py, telemetry.py, the hardening commit patch, and the new tests at head d46e9c15; confirmed the sanitization logic, hide_input_in_errors, static Graph messages, and _MUTATION_ERRORS coverage myself. Did NOT run tests locally (blocked by an ARM64 cryptography wheel build failure); the author reports 601 offline tests pass on Python 3.13, and Python 3.11 CI is still unchecked in the PR checklist. Coverage gaps: no local test execution, and no live MCP app host / Graph / backend validation. No new commits or external comments since this review.

Comment thread solutions/ess-maker-skills/src/mcp/agentconfig_org_announcements/server.py Outdated
Comment thread solutions/ess-maker-skills/src/mcp/agentconfig_org_announcements/server.py Outdated
Base automatically changed from users/rebova/org-announcements-review-audience to users/rebova/org-announcements-prerelease September 25, 2026 18:20
Expose separate announcement manager and editor entry points with validated flat arguments, scoped retry context, captured directory leases, and widget-owned mutations. Preserve indeterminate and committed-refresh recovery, safe telemetry, and independent provider discovery.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: eddd3818-bb74-42d3-bcf3-7e0670a57f27
Describe read-only repairable copies and cover zero-write opening, priority preservation, unresolved audiences, backend action errors, and non-repeating committed-refresh failures.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 15e9d51c-c328-48e6-9948-8819f9e57f90
Sanitize model-visible backend failures and hide validation input values while preserving structured app-only mutation errors.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 19335911-b012-4b7f-b179-e4caeae7ea69
@SophieS0ng
SophieS0ng force-pushed the users/rebova/org-announcements-review-runtime branch from d46e9c1 to 78d5c31 Compare September 25, 2026 18:20
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 19335911-b012-4b7f-b179-e4caeae7ea69
@SophieS0ng
SophieS0ng marked this pull request as ready for review September 25, 2026 18:33
@SophieS0ng
SophieS0ng requested review from Dawn Jeong (daeunJe0ng) and a lite review from Copilot September 25, 2026 18:33

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot review overview

🟡 Changes recommended

Critical registration and bulletin-ID validation issues, along with additional unresolved security and lifecycle findings, remain.

Get a fresh assessment by requesting another Copilot review.

Review effort: Lite
Findings: 2 High severity · 1 Medium severity

Open (3)
What changed in this PR

Adds a scoped Org Announcements MCP runtime with widget resources, authoring operations, validation, telemetry, and expanded test coverage.

Changes:

  • Adds announcement discovery, opener, audience, authoring, and lifecycle tools.
  • Adds shared validation, origin controls, retry handling, and privacy-safe telemetry.
  • Expands protocol, lifecycle, isolation, discovery, and CI tests.
File Description
tests/​mcp/​test_import_isolation.py Cross-provider import-isolation tests
tests/​mcp/​agentconfig_org_announcements/​test_telemetry_privacy.py Telemetry privacy coverage
tests/​mcp/​agentconfig_org_announcements/​test_mcp_app_protocol.py MCP protocol and lifecycle coverage
tests/​mcp/​agentconfig_org_announcements/​test_drafts.py Draft validation coverage
tests/​mcp/​agentconfig_org_announcements/​test_authoring_client.py Client and route validation tests
tests/​mcp/​agentconfig_org_announcements/​test_authoring_client_lifecycle.py Client lifecycle and authentication tests
tests/​mcp/​agentconfig_core/​test_agent_discovery.py Announcement discovery coverage
tests/​mcp/​_mcp_modules.py Isolated MCP module loading
solutions/​ess-maker-skills/​src/​mcp/​agentconfig_org_announcements/​validation.py Shared title and bulletin ID validation
solutions/​ess-maker-skills/​src/​mcp/​agentconfig_org_announcements/​telemetry.py Privacy-safe operation telemetry
solutions/​ess-maker-skills/​src/​mcp/​agentconfig_org_announcements/​server.py Scoped MCP tools, resources, and error handling
solutions/​ess-maker-skills/​src/​mcp/​agentconfig_org_announcements/​drafts.py Safe draft and request validation
solutions/​ess-maker-skills/​src/​mcp/​agentconfig_org_announcements/​client.py Scoped announcement API client
.github/​workflows/​ci.yml Expanded MCP and announcement test execution

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 19335911-b012-4b7f-b179-e4caeae7ea69

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot review overview

🟡 Changes recommended

Unresolved critical client-lifecycle races and moderate validation, telemetry, credential, and import-isolation findings remain.

Get a fresh assessment by requesting another Copilot review.

Review effort: Lite
Findings: 2 High severity

Open (2)
Resolved since last review (3)
Previously missed (1)

In code that hasn't changed since last review

Medium severity Normalize credential errors as AuthenticationRequired

solutions/​ess-maker-skills/​src/​mcp/​agentconfig_org_announcements/​server.py:318

Initial credential-file/token validation failures are raised by the shared core as _LocalCredentialError, a ValueError, not as OSError/LockException. This handler lets them escape to the opener/mutation ValueError paths as InvalidRequest (and the discovery handlers can expose an unhandled tool exception), so a missing, malformed, or mismatched credential is not reported as the structured AuthenticationRequired failure that the widget can act on. Normalize credential-resolution failures here before returning the client.

Comment thread solutions/ess-maker-skills/src/mcp/agentconfig_org_announcements/server.py Outdated
Comment thread solutions/ess-maker-skills/src/mcp/agentconfig_org_announcements/server.py Outdated
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 19335911-b012-4b7f-b179-e4caeae7ea69
@SophieS0ng

Copy link
Copy Markdown

Follow-up for the previously-missed credential finding in the latest Copilot overview: fixed in ee1afed. The shared core now exposes the precise LocalCredentialError type, and the announcements server normalizes that type alongside lock/OS credential failures instead of broadly catching ValueError. Protocol regressions cover missing, unreadable, empty, malformed, tenant-mismatched, and account-mismatched local credentials; opener/mutations preserve the structured AuthenticationRequired envelope, discovery returns a safe tool error, and private credential/cache details are not exposed. Final local validation: 651 Org Announcements tests and 306 shared/foundation tests passed (1 expected Windows permission-bit skip), plus Ruff, compile, and git diff --check.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot review overview

🟡 Changes recommended

Moderate credential-handling and privacy issues remain unresolved.

Get a fresh assessment by requesting another Copilot review.

Review effort: Lite
Findings: 2 Medium severity

Open (2)
Resolved since last review (2)

Comment thread solutions/ess-maker-skills/src/mcp/agentconfig_org_announcements/telemetry.py Outdated
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 19335911-b012-4b7f-b179-e4caeae7ea69

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot review overview

🔵 Needs a closer look

Two moderate findings remain unresolved in server.py; the telemetry and instruction clarity nits should also be addressed.

Review effort: Lite
Findings: None

Resolved since last review (2)

@jiuqlo jiuqlo 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.

Three follow-up findings against 25a410f: model-visible discovery error sanitization and two cancellation-related client-lifecycle issues. Each was reproduced against the pinned runtime; these are distinct from the previously resolved threads.

result = await client.list_agent_configs()
except AgentConfigApiError as error:
failure = await _failure_from(error, client)
raise ToolError(failure.message) from None

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Important: sanitize backend errors on model-visible discovery tools. list_agent_configs forwards failure.message directly into ToolError, and search_agents does the same below. For HTTP 400/409/422, _classify_api_error preserves the backend Message verbatim, so these model-visible tools bypass the sanitization applied by _open_error_payload. I reproduced this with an HTTP 400 containing a synthetic private marker: both discovery tool errors exposed it, while the opener correctly returned the local generic message. Please share the model-facing error-message policy across discovery and the opener, keeping raw backend validation detail confined to app-only mutation results, and add regression coverage for both discovery tools.

try:
yield client
finally:
await release_client(client)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Important: make lease release cancellation-safe. This cleanup awaits release_client() inside the request cancellation scope. If client A has been retired and another request is constructing its replacement while holding _client_lock, cancelling A's operation under MCP's AnyIO CancelScope interrupts cleanup at the lock acquisition before its lease count is decremented. I reproduced A remaining in _retired_clients with one recorded user and never being closed after the cancelled operation and replacement construction had both finished. Please shield the release bookkeeping and teardown from request cancellation, and avoid holding the bookkeeping lock throughout potentially human-duration authentication. A regression should use AnyIO cancellation with a contended lock; a single plain Task.cancel() does not reproduce the same cleanup behavior.

async with _client_lock:
if _client is None:
try:
_client = await asyncio.to_thread(OrgAnnouncementsClient)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Important: preserve shared construction across request cancellation. Cancelling this await releases _client_lock, but asyncio.to_thread does not stop the running constructor. _client stays unset, so the next request starts a second constructor while the first browser sign-in may still be pending, and the first constructor's eventual result is abandoned. I reproduced two simultaneously active constructors by cancelling the first acquisition before unblocking authentication. Please track construction in a shared task/future whose lifetime is independent of any one request, shield individual waiters appropriately, and ensure the eventual result is either published or disposed of. Add cancellation coverage alongside the existing concurrent-first-request test.

@apurvabanka

Copy link
Copy Markdown
Contributor

Should-fix

1. Model-visible backend detail leak on the discovery tools. _classify_api_error copies the backend detail into failure.message for 400/409/422, and list_agent_configs / search_agents both do raise ToolError(failure.message) — bypassing the _MODEL_VISIBLE_BACKEND_MESSAGES sanitization that _open_error_payload applies. This is the same leak class that was fixed for the opener, still open on the other two model-visible tools. test_discovery_credential_failure_is_a_safe_tool_error covers only the credential construction path, not an AgentConfigApiError raised from the request. Suggest routing both through the same sanitizer and adding a 400/422 regression.

2. Discovery tools are absent from telemetry. list_agent_configs and search_agents are not in telemetry._OPERATIONS and call neither record_operation nor _LOGGER.warning. Every other tool records success and failure, so discovery failures are invisible on the dashboard. If that's deliberate, a short comment in _OPERATIONS would prevent it reading as an omission.

3. Cold sign-in can block unrelated lease releases. get_client correctly runs the blocking MSAL construction via asyncio.to_thread, but it holds _client_lock for the whole interactive prompt, and release_client needs that same lock. After a 401 retires a client that still has active leases, an unrelated in-flight tool call can block on its own release until a human finishes the browser sign-in. Constructing outside the lock and re-checking under it, or a separate lease-accounting lock, would remove the coupling.

Nits

  • transition_bulletin uses _DESTRUCTIVE_ANNOTATIONS for all four transitions, but only delete is destructive; archive / unarchive / moveToDraft advertise a stronger hint than they warrant.

  • WIDGET_ORIGIN = _resolve_widget_origin() at import time fails closed (correct), but a misconfigured origin surfaces as a raw ValueError traceback at MCP startup rather than an operator-readable message.

Make authoring construction and cleanup cancellation-safe, sanitize model-visible discovery failures, and cover every registered tool with privacy-safe telemetry.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 19335911-b012-4b7f-b179-e4caeae7ea69
@daeunJe0ng

Copy link
Copy Markdown
Contributor

[copilot-review]

Approve with nits. Re-reviewed at head 8b9a5465. Every substantive prior finding (model-visible backend-detail leaks, client-lifecycle and lease-release races, cancellation-safety, mutation route-key validation, backend codes out of diagnostics and telemetry) is resolved in source; only doc-level nits remain.

Still open at head 8b9a5465:

  • Nit, telemetry.py:8: the module docstring says the event carries "exactly four things" but then lists five bullets (adds error_category). Update the count to five or drop the word "four".
  • Already tracked (not re-raising): the two nits Apurva Banka (@apurvabanka) noted still stand at head, the transition_bulletin _DESTRUCTIVE_ANNOTATIONS applied to all four transitions (server.py:1314) and the import-time WIDGET_ORIGIN ValueError traceback (server.py:229). The #273 registration gap on server.py:288 is likewise already answered as owned by the next stacked slice.

Cross-PR: this provider is unreachable until #273 registers it in .vscode/mcp.defaults.json, so #273 should gate any rollout.

Note: the current head commit 8b9a5465 ("harden client lifecycle and telemetry") post-dates the last human and bot review comments. It resolves the outstanding cancellation-safety, discovery-leak, and discovery-telemetry items, but has not itself received a human sign-off.

Not verified: I did not run the test suites locally (no local execution) and did not exercise live Graph, Weve, or MCP-host integration. The PR-reported offline results and the host-envelope contract remain unverified by me.

@daeunJe0ng Dawn Jeong (daeunJe0ng) left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Looks good to merge for this urgent slice; the runtime is well-designed and every substantive prior finding is resolved at head 8b9a546.

Non-blocking follow-ups are in my earlier [copilot-review] comment above (doc nit plus the already-tracked nits); none gate merge.

@SophieS0ng
SophieS0ng merged commit 90f03a1 into users/rebova/org-announcements-prerelease Sep 26, 2026
7 checks passed
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.

7 participants