29 KiB
Enterprise MCP Mock Lab
Manager summary
The Enterprise MCP Mock Lab gives developers a safe, repeatable place to reproduce an enterprise MCP connection before involving a customer tenant. It separates the reusable protocol simulation from the local admin experience:
enterprise-mock-lab
-> enterprise-mcp-mock-server
-> MCP, OAuth, HTTP, and JSON-RPC contracts only
This matters because a single “failed to connect” message can hide very different causes. The lab records the expected and observed outcome and names the first failed phase—for example, OAuth discovery, token validation, MCP initialization, tool discovery, provider authorization, or provider execution. That lets a reviewer explain what failed and who can act on it without treating every failure as a generic MCP outage.
The lab is a development and regression-test foundation. It is not a replacement for testing against an approved Microsoft 365 or ServiceNow test tenant.
Purpose and non-goals
The lab is designed to:
- reproduce a deterministic OAuth and MCP lifecycle on a developer machine;
- model documented ServiceNow, Work IQ, Microsoft Enterprise MCP, and Agent 365 surfaces with synthetic data;
- inject one named fault at a time so the first failure stays unambiguous;
- expose immutable scenario revisions and expected-versus-observed results;
- preserve safe phase, category, and correlation evidence without returning secrets or request bodies;
- support both manual investigation and automated regression tests.
It does not:
- authenticate against Entra ID or a ServiceNow identity provider;
- call Microsoft Graph, Microsoft 365, Agent 365, or ServiceNow;
- prove tenant licensing, rollout, entitlement, Conditional Access, ACL, guardian, domain-separation, proxy, or patch behavior;
- validate production JWT signatures or issue tokens with authority outside this process;
- provide production persistence, multitenancy, high availability, or compliance controls;
- make a provider-observed claim unless evidence from an approved test tenant is separately captured and dated.
Package-first boundary
The reusable package is packages/enterprise-mcp-mock-server. It owns declarative contracts, provider profiles, the fault catalog, protocol behavior, the in-memory runtime, safe traces, and the probe. It must remain usable without Den or the EE app.
The local admin application is ee/apps/enterprise-mock-lab. It owns process configuration, authentication, CSRF/origin enforcement, instance lifecycle, the HTML interface, and the control-plane API. It consumes the package only through its public exports.
Dependency rules:
enterprise-mock-labmay depend onenterprise-mcp-mock-server.enterprise-mcp-mock-servermust not import Den, an EE app, OpenWork's MCP client, or a provider SDK.- Den must not import the EE app. A future Den test connects to a lab data-plane URL like any other remote MCP client.
- Provider differences belong in named, sourced profiles or faults—not hidden environment checks.
- Runtime secrets and ports belong to the app or test harness, never to package fixtures.
Two-plane threat model
The lab deliberately uses two kinds of loopback listener.
| Boundary | Purpose | Allowed data | Main protections |
|---|---|---|---|
| Control plane | Create instances, select scenarios, run probes, inspect safe events | Admin session, CSRF token, profile-dependent write-only synthetic OAuth client secret, sanitized instance state | Loopback-only host, 32+ character admin secret, constant-time comparison, bounded rate/session state, HttpOnly/SameSite=Strict session cookie, exact Origin and session checks before bounded body parsing, CSRF validation, 64 KiB request limit, restrictive browser headers |
| Data plane | Behave like one synthetic provider MCP endpoint | Synthetic OAuth artifacts, MCP messages, synthetic tools and records | Separate loopback port per instance, no admin routes, deterministic fake authority, bearer values excluded from safe traces |
| Provider boundary | Represent a Microsoft or ServiceNow operation | Synthetic records and provider-shaped error evidence | No outbound calls, no customer data, explicit fidelity and limitations |
The control plane defaults to plain HTTP because it is loopback-only development infrastructure. Do not bind it or an instance data plane to a LAN or public interface. Use a separate TLS/proxy harness when the behavior being tested is certificate trust, TLS interception, DNS, or egress.
There are three different secret concepts. Keep them separate:
- the lab admin secret unlocks the local control plane;
- the mock OAuth client secret represents a synthetic confidential client;
- any future real provider credential belongs outside this lab and must never be entered here.
The first two are local synthetic inputs, held in process memory and represented in responses only by configured/not-configured booleans. The OAuth secret is required for manual confidential-client profiles such as ServiceNow and Microsoft Enterprise; public-client Work IQ and Agent 365 profiles leave it blank.
Start the lab
From the repository root, install dependencies once, generate a fresh local admin secret, and start the app:
pnpm install
export ENTERPRISE_MOCK_LAB_ADMIN_SECRET="$(node -e 'console.log(require("node:crypto").randomBytes(32).toString("base64url"))')"
printf 'Local lab admin secret: %s\n' "$ENTERPRISE_MOCK_LAB_ADMIN_SECRET"
pnpm --filter @openwork-ee/enterprise-mock-lab dev
Open http://127.0.0.1:8794 and paste the generated admin secret. The control plane starts with no mock instances; creating an instance does not start it automatically.
Process configuration
| Variable | Required | Default | Contract |
|---|---|---|---|
ENTERPRISE_MOCK_LAB_ADMIN_SECRET |
Yes | none | At least 32 characters; checked in memory and never returned |
ENTERPRISE_MOCK_LAB_HOST |
No | 127.0.0.1 |
Only 127.0.0.1 or ::1 |
ENTERPRISE_MOCK_LAB_PORT |
No | 8794 |
Control-plane port, 1–65535 |
ENTERPRISE_MOCK_LAB_SESSION_TTL_SECONDS |
No | 3600 |
Admin session lifetime, 300–86400 seconds |
Each instance also has an independent data-plane port, provider profile, scenario revision, and write-only synthetic credentials. A port can belong to only one lab instance at a time.
Provider profiles and fidelity
Fidelity is a claim with evidence, not a marketing label:
spec-conformant: behavior is asserted against a named stable protocol specification;provider-documented: the shape follows dated official provider documentation but has not been observed in a live tenant;provider-observed: behavior was captured from an approved test tenant with dated, redacted evidence;synthetic: useful lab behavior without a provider-equivalence claim.
Fidelity is recorded per aspect rather than inherited from one broad provider label. provenance.aspectFidelity independently identifies endpoint, authorization, catalog, tool-schema, provider-result, and transport evidence. Provider OAuth field values follow dated documentation where stated, but authorization behavior is labelled synthetic because the lab does not emulate Entra or ServiceNow identity systems. The provider profiles use synthetic identities and results even where their endpoint or catalog is provider-documented. A separate standards profile is spec-conformant and intentionally makes no Microsoft or ServiceNow product claim.
| Profile ID | Product surface | Documented auth shape | Verified | Important boundary |
|---|---|---|---|---|
synthetic-enterprise-oauth-mcp |
Standards-conformance OAuth/MCP surface | DCR or manual registration; mcp.read, mcp.write, offline_access |
2026-07-10 | Represents no Microsoft or ServiceNow product |
servicenow-inbound-quickstart |
ServiceNow MCP Server Console inbound Quickstart server | Manual registration; mcp_server acquisition and resource scope |
2026-07-10 | Customer family, patch, plugins, ACLs, domain separation, and transport behavior must be rechecked |
microsoft-work-iq |
Microsoft Work IQ MCP | api://workiq.svc.cloud.microsoft/WorkIQAgent.Ask plus offline_access; audience api://workiq.svc.cloud.microsoft |
2026-07-12 | Ten tool names/input names and types are documented; bounded schemas, mutation safety extensions, and results are explicitly synthetic |
microsoft-enterprise |
Microsoft MCP Server for Enterprise at https://mcp.svc.cloud.microsoft/enterprise |
Resource api://e8c77dc2-69b3-43f4-bc51-3213c9d915b4; documented token-request scope {resource}/.default |
2026-07-12 | Real tenants must grant enabled delegated MCP.* permissions; schemas/results, revocation, app-only, sovereign cloud, and transport behavior are not claimed |
agent-365-mail-v1-2026-07 |
Work IQ Mail / Agent 365 mcp_MailTools, V1 manifest snapshot |
McpServers.Mail.All plus offline_access; audience api://05879165-0320-489e-b644-f72b33f3edf0 |
2026-07-12 | Preview; endpoint, manifest, and ten dated tool names are documented, while argument schemas/results are explicitly synthetic and rollout/eligibility need live revalidation |
The UI shows each profile's stable fixture version, documentation links, verified date, per-aspect fidelity, and known limitations. Scenario evidence pins the fixture version; review those fields before using a profile as the basis for a product decision.
Profile source register (retrieved or rechecked through 2026-07-12 unless a profile records an earlier verification date):
- MCP stable Streamable HTTP transport and authorization;
- ServiceNow create an MCP server and connect an MCP server/client;
- Microsoft Work IQ MCP overview, permissions, and tool reference;
- Microsoft MCP Registry EnterpriseMCP entry, MCP Server for Enterprise getting started, and Copilot Studio connection fields;
- Microsoft Work IQ Mail MCP and Agent 365 developer tooling.
Declarative scenarios and revisions
A scenario is data, not a web of ad hoc conditionals. Its essential shape is:
{
"schemaVersion": 1,
"id": "servicenow-inbound-quickstart-provider-authorization-denied",
"revision": 2,
"profileId": "servicenow-inbound-quickstart",
"profileFixtureVersion": "2026-07-12.1",
"protocol": {
"version": "2025-11-25",
"responseMode": "json",
"requireSession": true,
"pageSize": 2
},
"oauth": {
"registration": "manual",
"clientId": "enterprise-mcp-test-client",
"redirectUris": ["http://127.0.0.1:19876/mcp/oauth/callback"],
"authorizationScopes": ["mcp_server"],
"requiredResourceScopes": ["mcp_server"]
},
"activeFault": {
"id": "provider-authorization-denied",
"trigger": { "occurrence": "always" }
},
"expected": {
"outcome": "failure",
"firstFailedPhase": "PROVIDER_AUTHORIZATION",
"category": "provider_authorization_denied"
}
}
The lab allows one active fault by default. Applying a change creates a new positive integer revision. The caller must send expectedRevision; a stale write receives a conflict instead of silently overwriting another developer's scenario. A request uses the scenario revision it started with, so a mid-request admin change cannot make its evidence internally inconsistent.
Scenario activation has an explicit credential-continuity choice. The manager-facing form selects preserve-compatible-oauth for catalog and provider-operation iteration: after draining active requests, the lab keeps only compatible, unexpired OAuth client/access/refresh authority and requires the client to create a new MCP session. Pending authorization codes, prior MCP sessions, operation records, fault counters, and prior events never cross the revision boundary. Preservation is rejected if the fixed endpoint/resource, provider fixture, client registration, exact redirects, or scopes changed. API callers can send reset for the original full reset behavior, and omission still means reset for backward compatibility. OAuth-layer faults require full reset and a fresh Connect; an already-issued credential cannot honestly exercise discovery, registration, consent, or token acquisition again.
Reset clears runtime state and safe events but intentionally keeps the active scenario. To recover to the healthy baseline, reset the instance, apply a new revision with no fault, and probe again.
Fault-to-first-failure matrix
The following matrix is the exact initial fault contract. “Error source” names the boundary that should own the first actionable explanation; the category is the machine-readable value returned by the probe.
Each fault also has a machine-readable diagnostic level. connection means OAuth/MCP connectivity failed; readiness means the connection worked but the catalog is not usable under this lab's enterprise policy; operation means connection and catalog succeeded before a provider/tool operation failed. This distinction prevents an empty but protocol-valid catalog or a provider ACL denial from being reported as a generic connection outage.
| Fault ID | First failed phase | Error category | Error source |
|---|---|---|---|
oauth-missing-auth-challenge |
AUTH_RESOURCE_DISCOVERY |
oauth_discovery_resource |
MCP resource server's unauthenticated OAuth challenge |
oauth-malformed-resource-metadata |
AUTH_RESOURCE_DISCOVERY |
oauth_discovery_resource |
OAuth protected-resource metadata document |
oauth-issuer-mismatch |
AUTH_ISSUER_DISCOVERY |
oauth_discovery_issuer |
Authorization-server metadata issuer |
oauth-pkce-s256-unsupported |
AUTH_ISSUER_DISCOVERY |
oauth_pkce_unsupported |
Authorization server's PKCE capabilities |
oauth-dynamic-registration-unsupported |
AUTH_CLIENT_REGISTRATION |
oauth_client_registration |
OAuth dynamic client registration endpoint |
oauth-invalid-client |
AUTH_TOKEN_ACQUISITION |
oauth_client_registration |
Token endpoint client authentication; Microsoft fixtures emit safe AADSTS7000215 trace/correlation evidence, while ServiceNow fixtures emit provider-specific credential remediation. Both remain synthetic fixtures, not captured customer responses. |
oauth-invalid-grant |
AUTH_TOKEN_ACQUISITION |
oauth_token |
Token endpoint authorization grant validation |
oauth-wrong-resource-audience |
AUTH_RESOURCE_VALIDATION |
oauth_wrong_audience |
MCP resource server's token audience/resource validation |
oauth-insufficient-scope |
AUTH_RESOURCE_VALIDATION |
oauth_insufficient_scope |
MCP resource server's required scope validation |
mcp-version-unsupported |
MCP_VERSION |
mcp_version |
MCP version negotiation |
mcp-initialize-malformed |
MCP_INITIALIZE |
mcp_initialize |
MCP initialize response/JSON-RPC correlation |
mcp-session-expired |
CONTINUITY_SESSION |
mcp_session_expired |
MCP session continuity on a post-initialize request |
mcp-initialized-rejected |
MCP_INITIALIZED |
mcp_lifecycle |
MCP initialized-notification lifecycle |
mcp-wrong-content-type |
MCP_TRANSPORT |
mcp_transport |
Streamable HTTP response content type |
mcp-broken-sse |
MCP_TRANSPORT |
mcp_transport |
Streamable HTTP SSE framing |
mcp-empty-tool-catalog |
MCP_TOOL_DISCOVERY |
catalog_empty |
Enterprise readiness policy after a protocol-valid empty MCP catalog; not a connection failure |
mcp-catalog-cursor-loop |
MCP_TOOL_DISCOVERY |
mcp_pagination_loop |
MCP tool-catalog pagination |
mcp-duplicate-tool |
MCP_TOOL_DISCOVERY |
mcp_duplicate_tool |
MCP tool identity across catalog pages |
mcp-invalid-tool-schema |
MCP_TOOL_DISCOVERY |
mcp_invalid_tool_schema |
Tool input-schema contract |
provider-authorization-denied |
PROVIDER_AUTHORIZATION |
provider_authorization_denied |
ServiceNow ACL/domain role or Microsoft workload permission, after MCP succeeds |
provider-policy-denied |
PROVIDER_AUTHORIZATION |
provider_policy_denied |
Tenant governance, guardian, or platform policy, distinct from an ACL |
provider-throttled |
PROVIDER_EXECUTION |
provider_throttled |
Downstream provider quota/rate limit, with retry guidance |
provider-unavailable |
PROVIDER_EXECUTION |
provider_unavailable |
Downstream provider transient availability |
mutation-timeout-after-commit |
PROVIDER_EXECUTION |
mutation_indeterminate |
Provider mutation outcome after a disconnect; reconcile before any replay |
Fault applicability is all profiles except for two deliberate boundaries: oauth-dynamic-registration-unsupported applies only to synthetic-enterprise-oauth-mcp, because the documented enterprise profiles use manual registration; mutation-timeout-after-commit applies only to profiles with mutation tools and therefore excludes the read-only microsoft-enterprise profile.
Live Microsoft 365 lesson represented by the fixture
On 2026-07-13, a development Microsoft 365 connection completed provider sign-in and returned to the Den API callback, then failed at token acquisition with HTTP 401, OAuth invalid_client, and Entra code AADSTS7000215. The saved tenant and client IDs matched the active app registration, but Den held a different client-secret value. Replacing the secret value and starting a fresh authorization completed the connection.
That incident proves why the lab separates authorization from token acquisition and why a plain Token request failed with status 401 is insufficient. The Microsoft oauth-invalid-client fixture reproduces the safe error code and correlation shape. It does not contain the development tenant, application ID, secret, authorization code, token, or customer content, and it remains a synthetic Entra response rather than a claim of live-provider conformance.
Configuration errors, DNS, TCP, TLS, proxy/WAF behavior, and real browser consent are intentionally outside the in-process fault catalog. Test them with a bad hostname, a reserved closed port, a self-signed TLS harness, or the target enterprise network. Do not fake those failures as an HTTP response from an already-reached server; that would assign the wrong first failed phase.
Manual UI verification
Use this short review path first:
- Start the lab and sign in. Confirm the page says Private loopback control plane and shows no instances.
- In Den, begin a manual/pre-registered OAuth connection for the planned lab endpoint, copy the callback URI Den shows, and stop before Connect.
- Create an instance named
ServiceNow manager reviewwith profileservicenow-inbound-quickstart, that exact callback URI, a free data-plane port, and client secretsynthetic-client-secret-local. Confirm it is STOPPED, the exact registered callback is visible, and the secret is shown only as configured. - Click Start, then Connect in Den. Confirm the instance is RUNNING and its MCP URL ends in
/sncapps/mcp-server/mcp/sn_mcp_server_default. - Select
operation · PROVIDER_AUTHORIZATION · Provider ACL deniedand apply the revision. Confirm the revision increments and the explainer names the operation diagnostic level,PROVIDER_AUTHORIZATION, andprovider_authorization_denied. - Click Run probe. Confirm Expectation matched and that expected and observed both report failure at
PROVIDER_AUTHORIZATIONwith categoryprovider_authorization_denied. - Click Reset. Confirm runtime evidence clears while the configured fault remains.
- Select Healthy baseline, apply the next revision, and run the probe. Confirm expected and observed both report success with no failed phase or category.
- Click Delete. Confirm the page returns to No instances yet and the data-plane port is no longer listening.
At every step, inspect the safe event timeline. It may contain phase, event, correlation ID, and request method; it must not contain the admin secret, OAuth client secret, authorization code, bearer token, session ID, request body, or tool arguments.
The control-plane browser origin and the OAuth callback are intentionally different concerns. Keep the lab UI on its literal loopback origin such as http://127.0.0.1:8794, but register Den's callback exactly as displayed. Microsoft Entra development registrations commonly use http://localhost:<port>/...; the fixture accepts that exact localhost callback and never rewrites it to 127.0.0.1.
API verification
The HTML interface and JSON API use the same control plane. The following local-only example needs curl and jq. It assumes the lab is already running and the admin secret remains in the current shell environment.
export LAB_ORIGIN=http://127.0.0.1:8794
export LAB_COOKIES="$(mktemp)"
export LAB_HTML="$(mktemp)"
curl --silent --show-error --location \
--cookie-jar "$LAB_COOKIES" \
--cookie "$LAB_COOKIES" \
--header "Origin: $LAB_ORIGIN" \
--header "Content-Type: application/x-www-form-urlencoded" \
--data-urlencode "adminSecret=$ENTERPRISE_MOCK_LAB_ADMIN_SECRET" \
"$LAB_ORIGIN/session/login" > "$LAB_HTML"
export LAB_CSRF="$(sed -n 's/.*name="csrfToken" value="\([^"]*\)".*/\1/p' "$LAB_HTML" | head -n 1)"
Create and start a ServiceNow-style instance:
export DATA_PORT=21080
export DEN_REDIRECT_URI="http://127.0.0.1:8790/v1/mcp-connections/oauth/callback"
CREATE_RESPONSE="$(curl --silent --show-error \
--cookie "$LAB_COOKIES" \
--header "Origin: $LAB_ORIGIN" \
--header "X-CSRF-Token: $LAB_CSRF" \
--header "Content-Type: application/json" \
--data "$(jq -nc --argjson port "$DATA_PORT" --arg redirectUri "$DEN_REDIRECT_URI" '{displayName:"ServiceNow API review",profileId:"servicenow-inbound-quickstart",port:$port,clientId:"synthetic-client",clientSecret:"synthetic-client-secret-local",redirectUris:[$redirectUri]}')" \
"$LAB_ORIGIN/api/v1/instances")"
export INSTANCE_ID="$(jq -r .id <<<"$CREATE_RESPONSE")"
export REVISION="$(jq -r .scenarioRevision <<<"$CREATE_RESPONSE")"
curl --silent --show-error \
--cookie "$LAB_COOKIES" \
--header "Origin: $LAB_ORIGIN" \
--header "X-CSRF-Token: $LAB_CSRF" \
--header "Content-Type: application/json" \
--data '{}' \
"$LAB_ORIGIN/api/v1/instances/$INSTANCE_ID/actions/start" | jq
Activate the provider authorization fault and prove its source:
FAULT_RESPONSE="$(curl --silent --show-error \
--cookie "$LAB_COOKIES" \
--header "Origin: $LAB_ORIGIN" \
--header "X-CSRF-Token: $LAB_CSRF" \
--header "Content-Type: application/json" \
--data "$(jq -nc --argjson revision "$REVISION" '{expectedRevision:$revision,faultId:"provider-authorization-denied"}')" \
"$LAB_ORIGIN/api/v1/instances/$INSTANCE_ID/scenario")"
export REVISION="$(jq -r .scenarioRevision <<<"$FAULT_RESPONSE")"
curl --silent --show-error \
--cookie "$LAB_COOKIES" \
--header "Origin: $LAB_ORIGIN" \
--header "X-CSRF-Token: $LAB_CSRF" \
--header "Content-Type: application/json" \
--data '{}' \
"$LAB_ORIGIN/api/v1/instances/$INSTANCE_ID/actions/probe" \
| jq '{matches: .lastProbe.matchesExpectation, expected: .lastProbe.expected, observed: .lastProbe.observed}'
The last command must return matches: true, with expected and observed both equal to:
{
"outcome": "failure",
"firstFailedPhase": "PROVIDER_AUTHORIZATION",
"category": "provider_authorization_denied"
}
Reset, recover healthy, and clean up:
curl --silent --show-error \
--cookie "$LAB_COOKIES" \
--header "Origin: $LAB_ORIGIN" \
--header "X-CSRF-Token: $LAB_CSRF" \
--header "Content-Type: application/json" \
--data '{}' \
"$LAB_ORIGIN/api/v1/instances/$INSTANCE_ID/actions/reset" > /dev/null
HEALTHY_RESPONSE="$(curl --silent --show-error \
--cookie "$LAB_COOKIES" \
--header "Origin: $LAB_ORIGIN" \
--header "X-CSRF-Token: $LAB_CSRF" \
--header "Content-Type: application/json" \
--data "$(jq -nc --argjson revision "$REVISION" '{expectedRevision:$revision,faultId:null}')" \
"$LAB_ORIGIN/api/v1/instances/$INSTANCE_ID/scenario")"
curl --silent --show-error \
--cookie "$LAB_COOKIES" \
--header "Origin: $LAB_ORIGIN" \
--header "X-CSRF-Token: $LAB_CSRF" \
--header "Content-Type: application/json" \
--data '{}' \
"$LAB_ORIGIN/api/v1/instances/$INSTANCE_ID/actions/probe" \
| jq '{matches: .lastProbe.matchesExpectation, observed: .lastProbe.observed}'
curl --silent --show-error \
--cookie "$LAB_COOKIES" \
--header "Origin: $LAB_ORIGIN" \
--header "X-CSRF-Token: $LAB_CSRF" \
--header "Content-Type: application/json" \
--data '{}' \
"$LAB_ORIGIN/api/v1/instances/$INSTANCE_ID/actions/delete"
rm -f "$LAB_COOKIES" "$LAB_HTML"
The healthy UI probe runs in fixture-conformance mode. It must return matches: true, outcome: success, and null for both firstFailedPhase and category; it proves OAuth, MCP initialization, the exact pinned tool-name set, and schema validity, but deliberately does not execute a provider tool or compare every schema field byte-for-byte.
Automated verification layers
No single test is allowed to stand in for all the others.
| Layer | What it proves | Command or owner |
|---|---|---|
| Contract/unit | Scenario schema, profile provenance, fault applicability, revisions, redaction, deterministic state | pnpm --filter @openwork/enterprise-mcp-mock-server test |
| Package quality | Public TypeScript surface and distributable build | pnpm --filter @openwork/enterprise-mcp-mock-server check |
| Protocol integration | OAuth discovery/token path, MCP initialize/lifecycle, pagination, tool calls, JSON/SSE, exact injected failures | Package integration tests |
| Control-plane security | Loopback configuration, login/rate limit, session cookie, Origin/CSRF, write-only secrets, safe errors | pnpm --filter @openwork-ee/enterprise-mock-lab test |
| App build | EE app imports only the public package contract and compiles as a standalone process | pnpm --filter @openwork-ee/enterprise-mock-lab build |
| Browser journey | Real lab process and browser: create, start, inject fault, match first phase, reset, recover, delete | The legacy flow runner was removed; executable coverage lives in evals/specs/. |
| Future Den consumer | Den connects over the instance URL and renders the same phase/category without importing the lab | Separate follow-up PR and Den-specific tests |
| Live-provider conformance | Target tenant, product, patch, permissions, policy, schemas, and provider IDs match reality | Approved Microsoft/ServiceNow test tenant; never silently run by this lab |
Extension rules for enterprise-*
New enterprise development packages and apps should follow these rules:
- Use kebab-case folder and package names that state one concern, such as
enterprise-mcp-mock-serverorenterprise-mock-lab. - Put reusable, browser-safe or runtime-neutral contracts in a package; put listeners, secrets, storage, and operator UI in an app.
- Export a small deliberate public API from the package root. Internal files are not consumer contracts.
- Make provider behavior declarative. Every new profile/fault includes an ID, phase, category, applicability, operator action, source URL, verified date, fidelity, limitation, and positive/negative tests.
- Preserve the first-failure rule. Do not turn OAuth, MCP, provider ACL, provider policy, throttling, and business errors into one exception.
- Keep fixtures synthetic and deterministic. No outbound calls, copied customer records, real tenant IDs, or credentials.
- Keep mutations approval- and idempotency-aware. A timeout after commit is indeterminate until reconciled; it is never a clean retry signal.
- Add capabilities without changing existing defaults invisibly. Breaking scenario or profile changes require an explicit schema/profile version or dated snapshot.
- Test the package directly, test the app boundary separately, then test the real consumer over the network contract.
Future Den relationship
Den is the first planned consumer, not the owner of this foundation. The follow-up connection-diagnostics PR should:
- launch or target a lab data-plane endpoint in development/test setup;
- use the same public remote MCP path it uses for a customer server;
- preserve the lab's first failed phase, category, safe message, and correlation evidence;
- show a clear admin explanation instead of “failed to connect”;
- never import
ee/apps/enterprise-mock-labor expose the lab control plane in production; - add Den-specific assertions without weakening the package and standalone-lab gates.
That order lets reviewers validate the test instrument first, then judge Den's diagnostic behavior against a known scenario, and finally repeat selected cases against approved provider test tenants.