fix: report OAuth metadata deployment errors
This commit is contained in:
Vendored
+27
-8
@@ -592,6 +592,7 @@ async function connectRemote(options, tokenStore, discoveryStore, serviceToken =
|
||||
async function loginRemote(options, tokenStore, discoveryStore, input = { browser: true }) {
|
||||
const writeLine = input.writeLine ?? ((value) => process.stderr.write(`${value}
|
||||
`));
|
||||
await requireOAuthProtectedResourceMetadata(options.url);
|
||||
let authorizationUrl;
|
||||
const provider = new GameCraftOAuthProvider(options, tokenStore, discoveryStore, async (url) => {
|
||||
authorizationUrl = url;
|
||||
@@ -610,7 +611,7 @@ ${url.toString()}`);
|
||||
});
|
||||
const callback = new OAuthCallbackServer(options.callbackPort, (state) => provider.validateState(state));
|
||||
await callback.listen();
|
||||
const firstClient = new Client({ name: "crafttable-mcp-cli", version: "0.1.0" });
|
||||
const firstClient = new Client({ name: "crafttable-mcp-cli", version: "0.1.1" });
|
||||
const firstTransport = new StreamableHTTPClientTransport(options.url, { authProvider: provider });
|
||||
try {
|
||||
try {
|
||||
@@ -646,7 +647,7 @@ async function logoutRemote(options, tokenStore, discoveryStore, localOnly, fetc
|
||||
return { hadCredential: Boolean(tokens), revoked: Boolean(tokens && !localOnly) };
|
||||
}
|
||||
async function connectWithTransport(options, transport, authentication) {
|
||||
const client = new Client({ name: "crafttable-mcp-cli", version: "0.1.0" });
|
||||
const client = new Client({ name: "crafttable-mcp-cli", version: "0.1.1" });
|
||||
try {
|
||||
await client.connect(transport);
|
||||
} catch (error) {
|
||||
@@ -662,10 +663,7 @@ async function connectWithTransport(options, transport, authentication) {
|
||||
};
|
||||
}
|
||||
async function discoverRevocationEndpoint(resource, fetchFn) {
|
||||
const metadataUrl = new URL(`/.well-known/oauth-protected-resource${resource.pathname === "/" ? "" : resource.pathname}`, resource.origin);
|
||||
const protectedResponse = await fetchFn(metadataUrl, { headers: { accept: "application/json" } });
|
||||
if (!protectedResponse.ok) throw new Error(`OAuth protected-resource discovery returned HTTP ${protectedResponse.status}`);
|
||||
const protectedMetadata = await protectedResponse.json();
|
||||
const protectedMetadata = await requireOAuthProtectedResourceMetadata(resource, fetchFn);
|
||||
const issuer = Array.isArray(protectedMetadata.authorization_servers) ? protectedMetadata.authorization_servers.find((value) => typeof value === "string") : void 0;
|
||||
if (!issuer) throw new Error("OAuth protected-resource metadata has no authorization server");
|
||||
const issuerUrl = new URL(issuer);
|
||||
@@ -684,6 +682,27 @@ async function discoverRevocationEndpoint(resource, fetchFn) {
|
||||
}
|
||||
throw new Error("OAuth authorization server does not advertise a revocation endpoint");
|
||||
}
|
||||
async function requireOAuthProtectedResourceMetadata(resource, fetchFn = fetch) {
|
||||
const metadataUrl = new URL(`/.well-known/oauth-protected-resource${resource.pathname === "/" ? "" : resource.pathname}`, resource.origin);
|
||||
let response;
|
||||
try {
|
||||
response = await fetchFn(metadataUrl, { headers: { accept: "application/json" } });
|
||||
} catch (error) {
|
||||
throw new Error(`Could not reach OAuth protected-resource metadata at ${metadataUrl}: ${errorMessage(error)}`);
|
||||
}
|
||||
const contentType = response.headers.get("content-type")?.toLowerCase() ?? "";
|
||||
if (!response.ok) {
|
||||
throw new Error(`OAuth is not enabled for ${resource}; protected-resource metadata at ${metadataUrl} returned HTTP ${response.status}`);
|
||||
}
|
||||
if (!contentType.includes("application/json")) {
|
||||
throw new Error(`OAuth is not enabled for ${resource}; protected-resource metadata at ${metadataUrl} returned ${contentType || "an unknown content type"} instead of JSON`);
|
||||
}
|
||||
try {
|
||||
return await response.json();
|
||||
} catch {
|
||||
throw new Error(`OAuth protected-resource metadata at ${metadataUrl} is not valid JSON`);
|
||||
}
|
||||
}
|
||||
async function revokeTokens(endpoint, clientId, tokens, fetchFn) {
|
||||
const candidates = [tokens.refresh_token ? { token: tokens.refresh_token, hint: "refresh_token" } : { token: tokens.access_token, hint: "access_token" }];
|
||||
for (const value of candidates) {
|
||||
@@ -718,7 +737,7 @@ async function serveBridge(options, tokenStore, discoveryStore, serviceToken = p
|
||||
}
|
||||
}
|
||||
function createProxyServer(upstream) {
|
||||
const server = new Server({ name: "crafttable-mcp-stdio-bridge", version: "0.1.0" }, {
|
||||
const server = new Server({ name: "crafttable-mcp-stdio-bridge", version: "0.1.1" }, {
|
||||
capabilities: {
|
||||
tools: {},
|
||||
resources: {}
|
||||
@@ -786,7 +805,7 @@ function keyringError(error) {
|
||||
}
|
||||
|
||||
// src/cli.ts
|
||||
var program = new Command().name("crafttable-mcp").description("OAuth client and stdio bridge for CraftTable MCP").version("0.1.0");
|
||||
var program = new Command().name("crafttable-mcp").description("OAuth client and stdio bridge for CraftTable MCP").version("0.1.1");
|
||||
withConnection(program.command("login").description("Log in through the system browser")).option("--no-browser", "print the authorization URL instead of opening it").option("--timeout <milliseconds>", "OAuth callback timeout", "600000").action(async (flags) => {
|
||||
const options = connectionOptions(flags);
|
||||
const timeoutMs = positiveInteger(flags.timeout, "OAuth callback timeout");
|
||||
|
||||
Reference in New Issue
Block a user