Files
tinker_tickets/mcp/src/Auth/McpIdentity.php
T

60 lines
1.8 KiB
PHP
Raw Normal View History

<?php
namespace TinkerTickets\Mcp\Auth;
/**
* The Tinker Tickets user an MCP request runs as, plus the token's scopes.
*
* Set once per HTTP request by IdentityMiddleware from the *validated* access
* token, which is held as server-side PSR-7 request attributes. It is
* deliberately NOT read from the JSON-RPC `_meta` field: clients can write
* to `_meta`, and the SDK's OAuthRequestMetaMiddleware only overwrites the
* keys the validator happens to set.
*
* Static state is request-scoped here: PHP-FPM serves one HTTP request per
* process lifecycle, and the whole MCP call (including SDK fibers) runs
* inside it.
*/
final class McpIdentity
{
public const SCOPE_READ = 'tickets:read';
public const SCOPE_WRITE = 'tickets:write';
private static ?array $user = null;
/** @var list<string> */
private static array $scopes = [];
/**
* @param array<string, mixed> $user
* @param list<string> $scopes
*/
public static function set(array $user, array $scopes): void
{
self::$user = $user;
self::$scopes = $scopes;
}
/**
* @return array<string, mixed>
*/
public static function user(): array
{
if (self::$user === null) {
// Unreachable if the middleware stack is intact; fail closed.
throw new \LogicException('MCP identity requested before IdentityMiddleware ran');
}
return self::$user;
}
/** tickets:write implies tickets:read (scope hierarchy, MCP spec §Scope Challenge Handling). */
public static function canRead(array $scopes): bool
{
return in_array(self::SCOPE_READ, $scopes, true) || in_array(self::SCOPE_WRITE, $scopes, true);
}
public static function canWrite(array $scopes): bool
{
return in_array(self::SCOPE_WRITE, $scopes, true);
}
}