@automattic/mcp-wordpress-remote 0.5.1 → 0.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,45 @@
1
+ # OAuth client behavior
2
+
3
+ The proxy is a public native OAuth client for its upstream MCP HTTP connection. The local MCP client talks to the proxy over stdio and does not need the WordPress tokens. The supported flow is authorization code with PKCE S256, using a configured public client ID or dynamic client registration. This implementation follows the [MCP authorization specification](https://modelcontextprotocol.io/specification/2026-07-28/basic/authorization) for that profile; it is not a claim to implement every optional authorization extension.
4
+
5
+ ## Discovery and trust
6
+
7
+ The first unauthenticated upstream request is the client's actual MCP request. Its Bearer challenge selects the protected resource metadata URL and required scopes. Without an explicit metadata URL, discovery tries the endpoint-specific well-known location before the origin fallback. Authorization-server discovery supports root and tenant issuers through OAuth metadata and both OIDC discovery layouts. Discovery documents must identify the expected resource or issuer before their endpoints are used.
8
+
9
+ OAuth endpoints require HTTPS. HTTP is allowed only for literal loopback addresses and localhost for local development. OAuth endpoint redirects are rejected. CUSTOM_HEADERS are attached to OAuth requests only when their destination has the same origin as WP_API_URL; they are not forwarded to an independently hosted authorization server. A server requiring separate authorization-server credentials needs a different supported integration; placing those secrets in CUSTOM_HEADERS does not grant cross-origin forwarding.
10
+
11
+ The provider verifies advertised S256 support before opening a browser. Resource indicators are included in authorization, code exchange, and refresh requests, preserving the metadata resource value exactly. Setting OAUTH_RESOURCE_INDICATOR=false fails with an explicit error. The native callback must bind to loopback.
12
+
13
+ ## Registration and stored credentials
14
+
15
+ New dynamic registrations declare application_type=native, token_endpoint_auth_method=none, and both authorization_code and refresh_token grants. The authorization server still decides whether to issue a refresh token. Existing issuer-bound registrations are reused; the proxy does not register a new client at every startup.
16
+
17
+ Client registrations and token records carry their issuing authorization server's issuer. A changed issuer cannot receive the old server's refresh token or registration credentials. A configured client ID already bound to another issuer fails with an actionable error; configure a client registered with the new server. Dynamic registrations can be replaced for the new issuer without forwarding the old credentials.
18
+
19
+ Legacy token records without an issuer can continue serving requests while their access token is valid. On expiry or rejection, the proxy starts a fresh authorization instead of sending an unbound refresh token. Unbound dynamic registrations are replaced once. This may require one new consent after upgrading from an older client. An existing bound registration that does not permit refresh can require a deliberate new registration; the proxy does not repeatedly register clients to probe grant policy.
20
+
21
+ The token directory remains namespaced by package version. Each resolved upstream resource URL has its own cache and coordination lock, so separate MCP endpoints or package versions can require separate authorization. Cross-process coordination assumes the processes share the same local cache directory and version; it does not coordinate different machines.
22
+
23
+ ## Renewal and permission recovery
24
+
25
+ Expired or server-rejected access tokens are renewed under the same atomic file lock used for browser authorization. Only the owner refreshes or opens a browser; waiting processes reuse the saved result. The refresh response replaces the token pair atomically. If the server omits a replacement refresh token, the original is retained. A stale rejection cannot delete a replacement token saved by another process.
26
+
27
+ Only invalid_grant falls back from refresh to browser authorization. Other refresh failures retain credentials and report failure. Waiters have a bounded timeout and fail if the owner ends or becomes stale without saving valid tokens; they do not each open their own login. A later request can recover an orphaned lock.
28
+
29
+ Initial authorization uses challenged scopes, otherwise explicitly configured scopes or protected metadata scopes. A Bearer insufficient_scope response can request a user-mediated upgrade that includes the prior granted scopes. Each challenged request retries once. Repeated identical scope challenges are not reauthorized, and each method/tool or resource operation allows at most two scope upgrades per connection. Persistent denial or insufficient permissions ends with an error; reconnect deliberately after changing permissions. An arbitrary 403 does not trigger authorization.
30
+
31
+ Interactive authorization pauses the upstream HTTP deadline while retaining request cancellation. OAUTH_TIMEOUT_MS bounds the interactive flow and lock wait; it defaults to 30 seconds and can be raised for slower consent flows.
32
+
33
+ ## Callback validation
34
+
35
+ State must match the pending attempt. If the response contains iss, it must match the recorded issuer exactly. If the server advertises authorization_response_iss_parameter_supported=true, missing iss is rejected; otherwise its absence is accepted for compatibility. These checks also apply to error callbacks. A valid denial ends the pending operation promptly.
36
+
37
+ The callback page acknowledges code receipt and tells the user to check the connection in their app. It does not claim that token exchange or storage succeeded. The legacy implicit-token endpoint is unavailable during a PKCE flow. Explicit legacy implicit flows require matching state and reject callbacks from untrusted origins. Implicit flow remains a compatibility mode outside this MCP OAuth 2.1 profile; its legacy coordinator is separate from PKCE coordination.
38
+
39
+ ## Scope and verification
40
+
41
+ Client ID Metadata Documents are recommended by MCP but require maintained hosted client metadata and are not provided by this package. Enterprise-managed authorization and client_credentials are optional extensions and are not implemented. OAuth token validation for API access remains the protected server's responsibility; the proxy does not assume access tokens are JWTs.
42
+
43
+ Regression coverage lives in oauth-discovery.test.ts, oauth-client-contract.test.ts, oauth-renewal.test.ts, wordpress-transport.test.ts, and oauth-processes.test.ts. Process tests use a fake authorization server and browser callback. Setting OAUTH_PROCESS_PROXY_PATH to an installed proxy bundle runs those scenarios against a packed artifact; OAUTH_PROCESS_BROWSER_MODULE supplies the test-only browser adapter during that check. Neither option changes production behavior.
44
+
45
+ Before publishing, run the repository's clean package gate and test healthy/broken upstream paths plus shared login, expiry refresh, rejected tokens, invalid_grant, transient errors, scope upgrades, denial and stale locks against the installed tarball. A live account's refresh in Codex must be checked after a release is published; synthetic fixtures are not evidence of that live outcome.
package/README.md CHANGED
@@ -6,7 +6,7 @@ Connect AI assistants like Claude Desktop to your WordPress sites with multiple
6
6
 
7
7
  ## Features
8
8
 
9
- - **MCP Authorization Specification Compliant** - Implements MCP Authorization specification 2025-06-18
9
+ - **MCP OAuth Client** - Authorization-code PKCE, validated discovery, issuer-bound credentials, and coordinated renewal
10
10
  - **OAuth 2.1 with PKCE** - Secure authorization code flow with PKCE (RFC 7636)
11
11
  - **Resource Indicators** - RFC 8707 compliance for token audience binding
12
12
  - **Dynamic Client Registration** - RFC 7591 support for automatic client registration
@@ -154,9 +154,9 @@ Tool-call hooks and `wpRequest` from the `/lib` bundle share the live authentica
154
154
 
155
155
  ## Authentication Methods
156
156
 
157
- ### 1. OAuth 2.1 (Recommended - MCP Compliant)
157
+ ### 1. OAuth 2.1 (Recommended)
158
158
 
159
- OAuth 2.1 provides the most secure and user-friendly experience with full MCP Authorization specification compliance.
159
+ OAuth 2.1 uses a public native client with PKCE S256, validated MCP discovery, and coordinated token renewal. See [OAuth client behavior](Docs/authentication.md) for the supported profile, migration behavior, and optional features.
160
160
 
161
161
  #### **For Self-Hosted WordPress Sites:**
162
162
 
@@ -185,9 +185,9 @@ OAuth 2.1 provides the most secure and user-friendly experience with full MCP Au
185
185
 
186
186
  **Benefits:**
187
187
 
188
- - Full compliance with MCP Authorization specification 2025-06-18
188
+ - Public native-client authorization following the MCP 2026-07-28 authorization requirements
189
189
  - Enhanced security with PKCE protection
190
- - One-time browser authorization
190
+ - Shared browser authorization and refresh-token renewal when supported
191
191
  - Tokens stored securely with automatic validation
192
192
  - Automatic endpoint discovery
193
193
  - No need to manage passwords
package/dist/lib.d.ts CHANGED
@@ -15,7 +15,7 @@ interface WordPressResponse {
15
15
  [key: string]: any;
16
16
  }
17
17
 
18
- declare const MCP_WORDPRESS_REMOTE_VERSION = "0.5.1";
18
+ declare const MCP_WORDPRESS_REMOTE_VERSION = "0.5.3";
19
19
  /**
20
20
  * Centralized configuration for MCP WordPress Remote
21
21
  * All default values are defined here and can be overridden via environment variables
@@ -155,25 +155,16 @@ declare const logger: {
155
155
  config: (message: string, level?: LogLevel, ...args: any[]) => void;
156
156
  };
157
157
 
158
- /**
159
- * Send one request to WordPress. While the proxy is running, the request rides its live
160
- * connection, and session recovery is left to the connected client.
161
- *
162
- * @param useJsonRpc Deprecated and ignored; requests are always sent as JSON-RPC.
163
- */
164
- declare function wpRequest(requestData: any, useJsonRpc?: boolean, options?: {
165
- allowSessionRecovery?: boolean;
166
- }): Promise<WordPressResponse>;
167
-
168
158
  /**
169
159
  * WordPress OAuth types and error definitions
170
- * Updated for MCP Authorization specification 2025-06-18 compliance
160
+ * Updated for MCP Authorization specification 2026-07-28 compliance
171
161
  */
172
162
  /**
173
163
  * WordPress OAuth tokens structure
174
164
  */
175
165
  interface WPTokens {
176
166
  access_token: string;
167
+ issuer?: string;
177
168
  token_type: string;
178
169
  expires_in?: number;
179
170
  scope?: string;
@@ -185,6 +176,11 @@ interface WPTokens {
185
176
  */
186
177
  interface WPClientInfo {
187
178
  client_id: string;
179
+ issuer?: string;
180
+ redirect_uris?: string[];
181
+ grant_types?: string[];
182
+ application_type?: string;
183
+ token_endpoint_auth_method?: string;
188
184
  client_secret?: string;
189
185
  authorization_endpoint?: string;
190
186
  token_endpoint?: string;
@@ -241,6 +237,16 @@ declare function isConfigError(error: any): error is ConfigError;
241
237
  declare function isAuthError(error: any): error is AuthError;
242
238
  declare function isAPIError(error: any): error is APIError;
243
239
 
240
+ /**
241
+ * Send one request to WordPress. While the proxy is running, the request rides its live
242
+ * connection, and session recovery is left to the connected client.
243
+ *
244
+ * @param useJsonRpc Deprecated and ignored; requests are always sent as JSON-RPC.
245
+ */
246
+ declare function wpRequest(requestData: any, useJsonRpc?: boolean, options?: {
247
+ allowSessionRecovery?: boolean;
248
+ }): Promise<WordPressResponse>;
249
+
244
250
  /**
245
251
  * Setup fetch polyfill for Node.js 18+ compatibility
246
252
  *