MCP Server — Conceptual Deep Dive
Experimental
The Agentweaver MCP server is experimental. Tool names, parameters, and behavior may change without notice. Pin to a known revision if you depend on the current surface.
Agentweaver's MCP server is an endpoint that tools can call, uses standards-based OAuth discovery, and preserves the caller's identity all the way to the Agentweaver API. The sections below explain its design as rebuildable ideas, so an engineer who has never seen Agentweaver could recreate the same behavior.
1. The mental model: MCP is the tool door, the API is the authority
Agentweaver has two separate responsibilities that are easy to conflate:
- The MCP server is an OAuth Resource Server (RS). It hosts the
/mcpresource and decides whether a request is allowed to invoke MCP tools. It does not issue OAuth tokens, run a login UI, store refresh tokens, or decide long-term identity policy. - The Agentweaver API is the OAuth Authorization Server (AS). It owns Microsoft Entra upstream login, consent, dynamic client registration, refresh-token rotation, revocation, JWT signing keys, and the business API that tools ultimately call.
The split matters because MCP clients already understand OAuth-style Resource Server behavior. When a client reaches a protected resource without credentials, the resource should tell the client where to discover authorization metadata. The client then obtains a token from the Authorization Server and retries the Resource Server with Authorization: Bearer ....
Agentweaver keeps the MCP server intentionally thin:
- Authenticate the caller at the MCP boundary.
- Expose Agentweaver capabilities as MCP tools.
- Forward the caller's bearer token to the backend API.
- Let the API enforce durable authorization, revocation, and data access.
That design avoids a confused-deputy problem. MCP accepts only Agentweaver broker tokens and forwards the validated token, so the API sees the real subject and applies project-specific policy. In stdio mode the operator supplies an Agentweaver broker token through AGENTWEAVER_TOKEN; no shared-key or upstream-provider-token fallback exists.
Key invariants to preserve when rebuilding:
- MCP HTTP mode protects tool calls as a Resource Server.
- The API issues and validates Agentweaver OAuth tokens as the Authorization Server.
- The access token audience is the MCP resource, normally
https://HOST/mcp. - Hosted deployments pin issuer and audience to the public host, even when services talk to each other over internal cluster addresses.
- The same bearer token that unlocked MCP is propagated downstream to the API.
Where this lives
apps/Agentweaver.Mcpapps/Agentweaver.Api/Auth/OAuthapps/Agentweaver.Api/Endpoints/OAuthServerEndpoints.csapps/Agentweaver.Api/Security
2. Runtime shape: local stdio and hosted HTTP solve different problems
The MCP server supports two transport modes.
stdio mode
Stdio mode is for local MCP hosts that spawn the process and talk over standard input/output. There is no inbound HTTP resource to authenticate, so AGENTWEAVER_TOKEN is required and must contain an Agentweaver broker token. The API independently validates it.
HTTP mode
HTTP mode exposes /mcp as a network resource. ASP.NET authentication delegates token validation to OpenIddict remote discovery/JWKS before tool dispatch. Health and RFC 9728 metadata are public.
The HTTP transport is intentionally stateless. Each request gets its own HTTP scope so the validated inbound token remains available during tool execution. That matters because the tool layer is mostly a proxy: it receives an MCP tool invocation, calls the Agentweaver API, and should forward the caller's token rather than losing context on a detached session loop.
The control flow is:
- Client sends an MCP request to
/mcp. - ASP.NET/OpenIddict authenticates or emits the RFC 9728-aware challenge.
- MCP SDK dispatches the requested tool.
- The tool calls the Agentweaver API through a shared client wrapper.
- The wrapper forwards only the authenticated broker token (or the required stdio broker token).
- The API performs its own validation and business operation.
Trade-off: stateless HTTP keeps identity propagation simple and reliable per request. If a future feature needs long-lived server-side MCP sessions, it must explicitly preserve caller identity across session work rather than relying on ambient HTTP context.
Where this lives
apps/Agentweaver.Mcp/Program.csapps/Agentweaver.Mcp/AgentweaverApiClient.cs
3. Standards-based discovery: how an unauthenticated client learns where to log in
A generic MCP client should not need hard-coded Agentweaver OAuth URLs. The standards flow lets the protected resource advertise enough information for the client to bootstrap itself.
Agentweaver uses two complementary discovery documents:
- RFC 9728 Protected Resource Metadata, served by the MCP Resource Server.
- RFC 8414 Authorization Server Metadata, served by the Agentweaver API Authorization Server.
3.1 The Resource Server challenge
When a client calls /mcp without a usable token, the MCP server returns 401 Unauthorized with a WWW-Authenticate: Bearer ... challenge. The important part is the resource_metadata parameter. It points the client at the protected-resource metadata document.
Conceptually, the challenge says:
"This is a bearer-protected MCP resource. Before you can call it, discover this resource's OAuth metadata here."
The response should not require authentication; otherwise a client would need a token in order to learn how to get a token.
3.2 Protected Resource Metadata (RFC 9728)
The MCP server publishes metadata for the /mcp resource. The document communicates four facts:
- resource: the exact protected resource identifier, e.g.
https://HOST/mcp. - authorization_servers: the issuer(s) that can issue tokens for that resource, e.g.
https://HOST. - bearer_methods_supported: Agentweaver expects bearer tokens in the HTTP
Authorizationheader. - scopes_supported: the logical permission needed for MCP invocation, currently
mcp:invoke.
The resource identifier is not just a URL for routing; it is also the OAuth audience. A token minted for a different audience should not unlock MCP.
3.3 Authorization Server Metadata (RFC 8414)
After reading the Resource Server metadata, the client discovers the Authorization Server metadata. That document advertises the authorization endpoint, token endpoint, JWKS endpoint, registration endpoint, revocation endpoint, supported grant types, and PKCE requirements.
Agentweaver's AS is deliberately public-client friendly: MCP clients use authorization code + PKCE, not a client secret. The server supports S256 PKCE and rejects weaker or missing challenge methods. The GitHub client secret stays server-side because the API brokers the GitHub login internally.
3.4 The path-suffixed well-known gotcha
The most important routing gotcha is that well-known discovery paths are not children of /mcp.
For a protected resource https://HOST/mcp, path-aware clients may probe well-known URLs like:
https://HOST/.well-known/oauth-protected-resourcehttps://HOST/.well-known/oauth-protected-resource/mcphttps://HOST/.well-known/oauth-authorization-server
This follows the well-known URI convention for issuers/resources with path components: the path suffix is appended after the well-known name. A Kubernetes route that only forwards PathPrefix /mcp will never deliver /.well-known/... requests to the MCP service. Agentweaver therefore routes the bare and /mcp-suffixed protected-resource metadata paths explicitly, before the /mcp prefix route.
This is a standards-compliance and interoperability decision, not a cosmetic duplicate. Some clients probe the suffixed form; serving only the bare form breaks those clients even though the resource itself is /mcp.

Where this lives
apps/Agentweaver.Mcp/Program.csapps/Agentweaver.Mcp/McpBrokerAuthenticationHandler.csapps/Agentweaver.Api/Endpoints/OAuthServerEndpoints.csdocs/mcp-oauth.mdk8s/base/mcp-httproute.yaml
4. Bearer tokens: what is accepted, why, and how identity survives
The MCP HTTP boundary accepts exactly one credential class: Agentweaver broker access tokens.
4.1 No token: challenge, do not guess
If there is no bearer token, MCP returns a discovery challenge. It does not redirect, start GitHub login itself, or invent a local login flow. Resource Servers should tell the client how to discover authorization; clients decide how to run the flow.
4.2 Agentweaver OAuth access tokens
Agentweaver-minted access tokens are signed JWTs. The API Authorization Server issues them after authorization code + PKCE, explicit consent, and Microsoft Entra upstream authentication.
The important claims are conceptual rather than implementation-specific:
- iss: the public Authorization Server issuer.
- aud: the MCP resource, normally
https://HOST/mcp. - sub: the authenticated Agentweaver subject.
- scope: includes
mcp:invoke. - jti: a unique token identifier used for revocation denylisting.
- exp / nbf / iat: short-lived token timing, with a small validation clock skew.
The MCP Resource Server validates these tokens offline with the AS public keys from JWKS. Offline validation keeps normal MCP requests fast and avoids a network call to the Authorization Server for every tool invocation. The trade-off is key caching: the Resource Server must refresh JWKS periodically and respect kid/key rotation behavior.
Validation should fail closed unless all of these are true:
- The token is structurally a JWT.
- The signature validates with an AS-published RSA signing key.
- The algorithm is the expected asymmetric signing algorithm.
- The issuer equals the configured public issuer.
- The audience equals the MCP resource.
- The token lifetime is valid.
- A usable subject identity is present.
The Resource Server additionally requires mcp:invoke, a non-empty subject, an RS256 algorithm, and a kid that resolves through the discovered JWKS. Missing tokens receive a challenge without an OAuth error; invalid tokens receive invalid_token; valid tokens without the required scope receive insufficient_scope.
Raw Entra tokens, GitHub tokens, API keys, unknown-key JWTs, and compatibility credentials are outside the trust boundary and fail closed.
4.3 Revocation and the jti denylist
MCP extracts the token identity, including jti, but the API owns the authoritative denylist. That is because the API owns OAuth token lifecycle: refresh-token rotation, revocation, and durable storage. MCP forwards the bearer token to the API; the API then rejects tokens whose jti has been revoked before natural expiry.
This works because current MCP tools are proxies to the API. If future MCP tools perform sensitive local work without calling the API, they must either perform the same denylist check or avoid relying solely on MCP-side JWT validation.
4.4 Public issuer/audience pinning
In Kubernetes, the browser and MCP clients use the public host, while pods call each other through internal service names. If either service derives issuer or audience from the internal request host, tokens will stop matching.
Example failure mode:
- The AS mints a token with
aud=https://public.example/mcp. - MCP forwards it to
http://agentweaver-api:8080. - The API derives expected audience as
http://agentweaver-api:8080/mcp. - Validation fails even though the user has a valid public token.
The fix is to pin issuer and audience to the configured public origin on both services. OpenIddict discovers metadata and JWKS remotely and refreshes signing configuration safely when keys rotate.
Where this lives
apps/Agentweaver.Mcp/McpBrokerAuthenticationHandler.csapps/Agentweaver.Mcp/McpOAuthConfiguration.csapps/Agentweaver.Mcp/AgentweaverApiClient.csapps/Agentweaver.Api/Auth/OAuthapps/Agentweaver.Api/Auth/AgentweaverAuthentication.csapps/Agentweaver.Api/Auth/EndpointAuthorization.csk8s/base/mcp-deployment.yamlk8s/base/api-deployment.yaml
5. The MCP tool surface: a protocol adapter over Agentweaver capabilities
The MCP server does not try to duplicate Agentweaver business logic. It exposes a tool vocabulary and translates MCP calls into API calls. This is the right boundary because:
- MCP clients need stable, discoverable tool names and JSON schemas.
- The API remains the source of truth for projects, runs, teams, memory, workflows, and policies.
- Authentication and authorization stay consistent because tool calls use the same API path as other clients.
- New capabilities can be added by adding API endpoints and thin MCP adapters, rather than building a second domain model inside MCP.
A rebuild should preserve this pattern:
- Define one tool group per product capability.
- Keep each tool method small: validate MCP-facing arguments, call the API, return the API result.
- Avoid hidden side effects in MCP that bypass API authorization or audit behavior.
- Let the MCP SDK generate protocol schemas from tool declarations.
- Forward the caller's bearer token for every backend call when one exists.
- URI-escape every API route path segment (
project_id,run_id,agent_name, task ids, workflow ids, etc.) before interpolation. Path parameters must be encoded withUri.EscapeDataString()so a malicious id containing../or/stays inside its segment instead of rewriting the target API route. - Surface actionable error detail. When a backend call fails, throw an
McpException(Agentweaver usesMcpApiException) carrying the mapped API error payload. The MCP SDK only appends the message text to the client-visible result when the thrown exception derives fromMcpException; any other exception type collapses to the opaqueAn error occurred invoking '<tool>'.wrapper that hides the real cause. - Give every optional tool parameter an explicit default (
string? model_id = null,int? target_index = null, and a trailingCancellationToken ct = default).Microsoft.Extensions.AImarks any parameter without a default asrequiredin the generated JSON schema regardless of C# nullability. A client that omits such a parameter triggers an argument-binding failure that throws before the tool body runs — a non-McpExceptionthe SDK collapses to the opaque wrapper (the root cause of #347).
Capability map
| Area | Conceptual purpose | Representative tools |
|---|---|---|
| Projects | Create, inspect, configure, rename, delete projects and list their runs. | project_list, project_get, project_create, project_configure, project_list_runs |
| Runs | Start or monitor agent work, inspect artifacts, retry, or archive. | run_task, run_submit, run_status, run_watch, run_review, run_show_artifacts, run_retry |
| Coordinator orchestration | Start coordinator runs, confirm/revise outcome specs, inspect work plans and child runs, steer active work. | coordinator_start, coordinator_outcome_spec_confirm, coordinator_work_plan_get, coordinator_steer, orchestration_topology |
| Backlog and board | Manage tasks through backlog/ready/problem/review/active/done flows and project pickup settings. | backlog_capture_task, backlog_get_board, backlog_move_to_ready, send_all_backlog_to_ready, backlog_set_settings |
| Memory, decisions, sessions | Submit and merge decision inbox entries, record/search memory, export/import .squad state, track sessions. | decision_inbox_submit, decision_inbox_merge, squad_decide, memory_record, memory_search, session_start |
| Team casting | Inspect and evolve a project's agent roster and role charters. | team_get, team_cast, team_member_add, team_member_retire, team_member_get_charter |
| GitHub capability helpers | Start and poll opaque browser handoffs for caller-scoped Repo App and Owner-authorized project Copilot App capabilities, disconnect those capabilities, or inspect redacted readiness. | github_repo_app_connect, github_repo_app_authorization_status, github_repo_app_disconnect, project_copilot_app_connect, project_copilot_app_authorization_status, project_copilot_app_disconnect, project_github_capability_status |
| Workspace browsing | Let clients list project workspace references, browse files, and fetch file contents through controlled API paths. | list_project_workspace_refs, list_project_workspace, get_project_workspace_file |
| Workflows | Discover, validate, generate, sync, and save workflow definitions. | workflows_list, workflow_get, workflows_sync, workflow_generate, workflow_save |
| Blueprints and catalog | List scenario/role catalogs and generate or validate project blueprints. | list_blueprints, validate_blueprint, blueprint_generate, catalog_list_roles |
| Diagnostics and sandbox policy | Expose operational status and repository sandbox policy settings. | diagnostics_get, heartbeat_status, sandbox_policy_get, sandbox_policy_set |
Beyond the static tool names and descriptions summarized here, the protocol-level schema may include additional MCP SDK-generated metadata.
run_submit is a legacy alias. It starts a coordinator orchestration in direct mode and rejects agent_name and base_branch. Use run_task or coordinator_start for new clients.
Where this lives
apps/Agentweaver.Mcp/Toolsapps/Agentweaver.Mcp/AgentweaverApiClient.cs
6. Kubernetes routing: expose /mcp, but do not hide discovery
The Kubernetes design has one public host and routes selected paths to the MCP service.
There are three important routing shapes:
- Health: public
/mcp/healthis rewritten to the pod's internal/healthz. This gives operators a stable public health URL without making the app serve health under the MCP protocol path. - Discovery: exact well-known protected-resource paths go to MCP and are unauthenticated. They must be explicit because they are outside
/mcp. - MCP protocol:
PathPrefix /mcpcarries normal MCP traffic to the MCP service.
The deployment passes Auth:OAuth:PublicOrigin to both services. MCP derives the exact <origin>/mcp resource and RFC 9728 metadata URL from that one canonical value.
In AKS, run npm run azure:provision-infra before the first npm run azure:deploy-from-local so the required mcp-api-key CSI secret is available. Without it, API authentication and worker loopback calls fail, and cluster diagnostics report key_vault: critical: secret 'mcp-api-key' not found.
Where this lives
k8s/base/mcp-service.yamlk8s/base/mcp-httproute.yamlk8s/base/mcp-deployment.yamlk8s/base/secret-provider-class.yaml
7. Rebuild checklist
If you were recreating Agentweaver's MCP server from scratch, build in this order:
- Define the MCP resource identity. Choose the public resource URI, e.g.
https://HOST/mcp, and treat it as the JWT audience. - Implement the Authorization Server separately. Publish RFC 8414 metadata, run authorization code + PKCE, broker GitHub login server-side, enforce org membership before issuing codes, sign short-lived RS256 JWTs, publish JWKS, support refresh and revocation.
- Implement the Resource Server discovery surface. Serve RFC 9728 protected-resource metadata unauthenticated at both bare and path-suffixed well-known URLs.
- Challenge correctly. Return
401 WWW-Authenticate: Bearer ... resource_metadata="..."for unauthenticated MCP requests. - Validate bearer tokens at MCP. Use ASP.NET/OpenIddict remote discovery/JWKS and accept only the exact Agentweaver broker-token contract.
- Propagate caller identity. Store the accepted bearer token in request context and forward it to the API for every tool call.
- Keep tools thin. Use MCP as a protocol adapter, not as a second implementation of Agentweaver's domain logic.
- Pin hosted OAuth identity. In Production, fail startup if the canonical public origin is missing or host-derived. Internal service DNS must not change token identity.
- Route discovery explicitly. Kubernetes or gateway routing must send
/.well-known/...paths to the right service;/mcpprefix routing is not enough. - Test with a generic client. Verify the full flow: unauthenticated
/mcpchallenge, protected-resource metadata, AS metadata, PKCE login, token redemption, MCP call with bearer, downstream API call with the same bearer.
8. Common failure modes and the invariant that prevents each one
| Failure mode | Why it happens | Preventing invariant |
|---|---|---|
| Client cannot discover OAuth metadata | Gateway only routes /mcp, but well-known URLs are outside that prefix. | Route bare and /mcp-suffixed well-known paths explicitly. |
| Token validates locally but fails in cluster | One service derived issuer/audience from an internal host. | Pin issuer and audience to public values in Production. |
| API sees every tool call as the MCP service | MCP used a shared backend key instead of the caller's token. | Forward the accepted bearer token to the API. |
| Revoked access token still passes MCP JWT validation | Offline JWT validation cannot see a central denylist by itself. | API performs authoritative jti denylist checks on forwarded tokens. |
| Upstream or service credentials are replayed at MCP | A compatibility fallback bypasses the broker trust boundary. | Accept only Agentweaver broker JWTs for the exact resource and scope. |
| Future local MCP tool bypasses authorization | Tool performs sensitive work without calling the API. | Either keep tools as API proxies or add equivalent local authorization and revocation checks. |
Tool returns opaque An error occurred invoking '<tool>'. | The exception reaching the SDK is not an McpException, or argument binding failed before the body ran because an optional parameter lacked a default and was schema-marked required. | Throw McpApiException for backend failures and give every optional parameter an explicit = null/= default. |
See also
- Agent definition — Deep Dive — the GitHub Copilot agent whose Tool map is generated from these MCP tools.
- MCP tool index — the generated list of every
agentweaver-*tool.
