Repository navigation
v0.5.85
Upgrading
- Run
kit:update plan|applyformcp(re-pinnedsha256:7e095959…f01b→sha256:29d14952…750e):Mcp/Authorize/Page.vueshows the redirect host and the unverified-app note below, and the migration0001_01_02_000603_create_kit_mcp_registrations_tableis new. Thenphp artisan migrate. - The
mcpfeature now serves its own/.well-known/oauth-authorization-serverand/.well-known/oauth-protected-resourcedocuments (root and path-suffixed) and no longer callsMcp::oauthRoutes(). A host that re-declared those routes over laravel/mcp's (to advertise its scopes) can drop them:scopes_supportedis the kit's token scopes, which include the host's. A host that re-declaredPOST /oauth/registerover laravel/mcp's controller can drop that too and setkit.mcp.registrationto true; its existing clients keep working.token_endpoint_auth_methods_supportedis now["none"]only. - A host's unregistered-
redirect_uritest that expected Passport's 401invalid_clientJSON for a browser request now gets the kit's 400 page; JSON requests keep the 401. Thelaravel/mcprequirement is^1.0.2. - A client that registered
http://localhost/...(orhttp://127.0.0.1/...,http://[::1]/...) now matches that redirect on any port at authorize and token time (RFC 8252 §7.3), as Claude Code's per-session port needs. Only the port is free: the scheme, host, path and query still match exactly, and nothing changes forhttpsredirects. - New migration for
mcp,0001_01_02_000603_create_kit_mcp_registrations_table(thekit_mcp_registrationstable: which OAuth clients came fromPOST /oauth/register). Runkit:update plan|applyformcp, thenphp artisan migrate. Until it is migrated, registration answers 500server_errorandkit:mcp:clientsrefuses. A client registered before (laravel/mcp's registration, or the kit's before this table) is not in it:php artisan kit:mcp:clients --adopt=<client id>records one by hand, so that--revokeand--prune-unusedcan touch it; the kit never guesses.
Added
- MCP client registration and discovery, the kit's own (backported from main; docs/mcp-registration.md). Opt-in OAuth 2.0 Dynamic Client Registration (RFC 7591) at
POST /oauth/register,kit.mcp.registration(KIT_MCP_REGISTRATION, off by default: the route does not exist), for public clients only: an assistant such as Claude registers itself with no secret issued and no management credentials. Redirect URIs are checked at registration:httpswith a host,httpto a loopback host on any port (Claude Code's per-session port, RFC 8252), or a private-use scheme the host lists inkit.mcp.redirect_schemes(empty by default); never a wildcard, fragment, userinfo, plainhttpelsewhere, orjavascript,data,blob,file. A per-IP rate limit (kit.mcp.registration_per_minute, 10) and an optionalkit.mcp.registration_policy(aRegistrationPolicyclass refusing with a reason, 403access_denied). The client is the Passport row laravel/mcp's registration stored, so a client registered before keeps working, with no column added. The discovery documents (RFC 8414 and RFC 9728, root and/mcp) are now the kit's, under laravel/mcp's route names, advertising only what the kit implements:code,authorization_codeandrefresh_token, S256, public clients, the kit's token scopes, andregistration_endpointonly when registration is on;Mcp::oauthRoutes()is no longer called. The 401 on/mcpaddsscope="mcp:use"to laravel/mcp's challenge. Passport's client bridge matches a loopbacklocalhostredirect without its port, as league already does for127.0.0.1.kit:mcp:clientslists registered clients,--revoke=<id>revokes one with its tokens,--prune-unused=<days>revokes those with no token in that time (--dry-runto look first).composer test:mcp-registrationcovers each RFC requirement, the refusals, the limit, the policy, the metadata shape, a pre-existing client and registration off. - The consent page names where the assistant sends you. It shows the redirect host Passport verified for the request, and for a self-registered client (dynamic client registration, or an owner-less public client shaped like one) adds "Unverified app": the name is the client's own claim, so a person can tell a lookalike from the app they expect.
AuthorizeViewModelgainsredirect_hostandregistered; theMcp/Authorize/Page.vuestub shows them. - PKCE S256 required at authorize. Every authorization request must carry a
code_challenge(43 to 128 unreserved characters) withcode_challenge_method=S256; a missing challenge or theplainmethod is refused with 400 before league sees it, never redirected. Claude and every MCP client send S256 already. - The OAuth error page, brandable. When an authorization request cannot go back to its app (unknown client, unregistered
redirect_uri, bad PKCE), a browser now gets a readable 400 page instead of Passport's bareinvalid_clientJSON; JSON callers and errors sent back to a verified redirect URI are unchanged. The kit'skit::oauth-errorview by default; a host setskit.mcp.oauth_error_viewto its own view, orOAuthErrorPage::using(fn (Request, string $error, ?string $description): Response)for its own page (an Inertia error page, say). A host that subclassed Passport's authorization controller for this can drop that.
What's Changed
- [0.5.x] MCP dynamic client registration, kit-owned discovery, PKCE S256, brandable OAuth error page (backport of #406) by @jaspertey in #419
Full Changelog: v0.5.84...v0.5.85