Merge branch 'main' into meta_properies_in_tools
This commit is contained in:
commit
a5d5593c97
10 changed files with 184 additions and 53 deletions
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@modelcontextprotocol/inspector-client",
|
||||
"version": "0.17.2",
|
||||
"version": "0.17.3",
|
||||
"description": "Client-side application for the Model Context Protocol inspector",
|
||||
"license": "MIT",
|
||||
"author": "Anthropic, PBC (https://anthropic.com)",
|
||||
|
|
|
|||
|
|
@ -113,8 +113,8 @@ const DynamicJsonForm = forwardRef<DynamicJsonFormRef, DynamicJsonFormProps>(
|
|||
setJsonError(errorMessage);
|
||||
|
||||
// Reset to default for clearly invalid JSON (not just incomplete typing)
|
||||
const trimmed = jsonString.trim();
|
||||
if (trimmed.length > 5 && !trimmed.match(/^[\s[{]/)) {
|
||||
const trimmed = jsonString?.trim();
|
||||
if (trimmed && trimmed.length > 5 && !trimmed.match(/^[\s[{]/)) {
|
||||
onChange(generateDefaultValue(schema));
|
||||
}
|
||||
}
|
||||
|
|
@ -155,7 +155,7 @@ const DynamicJsonForm = forwardRef<DynamicJsonFormRef, DynamicJsonFormProps>(
|
|||
|
||||
const formatJson = () => {
|
||||
try {
|
||||
const jsonStr = rawJsonValue.trim();
|
||||
const jsonStr = rawJsonValue?.trim();
|
||||
if (!jsonStr) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -171,7 +171,7 @@ const DynamicJsonForm = forwardRef<DynamicJsonFormRef, DynamicJsonFormProps>(
|
|||
const validateJson = () => {
|
||||
if (!isJsonMode) return { isValid: true, error: null };
|
||||
try {
|
||||
const jsonStr = rawJsonValue.trim();
|
||||
const jsonStr = rawJsonValue?.trim();
|
||||
if (!jsonStr) return { isValid: true, error: null };
|
||||
const parsed = JSON.parse(jsonStr);
|
||||
// Clear any pending debounced update and immediately update parent
|
||||
|
|
|
|||
|
|
@ -225,16 +225,18 @@ const ToolsTab = ({
|
|||
...params,
|
||||
[key]: checked
|
||||
? null
|
||||
: prop.default !== null
|
||||
? prop.default
|
||||
: prop.type === "boolean"
|
||||
? false
|
||||
: prop.type === "string"
|
||||
? ""
|
||||
: prop.type === "number" ||
|
||||
prop.type === "integer"
|
||||
? undefined
|
||||
: undefined,
|
||||
: prop.type === "array"
|
||||
? undefined
|
||||
: prop.default !== null
|
||||
? prop.default
|
||||
: prop.type === "boolean"
|
||||
? false
|
||||
: prop.type === "string"
|
||||
? ""
|
||||
: prop.type === "number" ||
|
||||
prop.type === "integer"
|
||||
? undefined
|
||||
: undefined,
|
||||
})
|
||||
}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -102,16 +102,40 @@ export const clearClientInformationFromSessionStorage = ({
|
|||
sessionStorage.removeItem(key);
|
||||
};
|
||||
|
||||
export const getScopeFromSessionStorage = (
|
||||
serverUrl: string,
|
||||
): string | undefined => {
|
||||
const key = getServerSpecificKey(SESSION_KEYS.SCOPE, serverUrl);
|
||||
const value = sessionStorage.getItem(key);
|
||||
return value || undefined;
|
||||
};
|
||||
|
||||
export const saveScopeToSessionStorage = (
|
||||
serverUrl: string,
|
||||
scope: string | undefined,
|
||||
) => {
|
||||
const key = getServerSpecificKey(SESSION_KEYS.SCOPE, serverUrl);
|
||||
if (scope) {
|
||||
sessionStorage.setItem(key, scope);
|
||||
} else {
|
||||
sessionStorage.removeItem(key);
|
||||
}
|
||||
};
|
||||
|
||||
export const clearScopeFromSessionStorage = (serverUrl: string) => {
|
||||
const key = getServerSpecificKey(SESSION_KEYS.SCOPE, serverUrl);
|
||||
sessionStorage.removeItem(key);
|
||||
};
|
||||
|
||||
export class InspectorOAuthClientProvider implements OAuthClientProvider {
|
||||
constructor(
|
||||
protected serverUrl: string,
|
||||
scope?: string,
|
||||
) {
|
||||
this.scope = scope;
|
||||
constructor(protected serverUrl: string) {
|
||||
// Save the server URL to session storage
|
||||
sessionStorage.setItem(SESSION_KEYS.SERVER_URL, serverUrl);
|
||||
}
|
||||
scope: string | undefined;
|
||||
|
||||
get scope(): string | undefined {
|
||||
return getScopeFromSessionStorage(this.serverUrl);
|
||||
}
|
||||
|
||||
get redirectUrl() {
|
||||
return window.location.origin + "/oauth/callback";
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ export const SESSION_KEYS = {
|
|||
PREREGISTERED_CLIENT_INFORMATION: "mcp_preregistered_client_information",
|
||||
SERVER_METADATA: "mcp_server_metadata",
|
||||
AUTH_DEBUGGER_STATE: "mcp_auth_debugger_state",
|
||||
SCOPE: "mcp_scope",
|
||||
} as const;
|
||||
|
||||
// Generate server-specific session storage keys
|
||||
|
|
|
|||
|
|
@ -112,6 +112,8 @@ jest.mock("../../auth", () => ({
|
|||
})),
|
||||
clearClientInformationFromSessionStorage: jest.fn(),
|
||||
saveClientInformationToSessionStorage: jest.fn(),
|
||||
saveScopeToSessionStorage: jest.fn(),
|
||||
clearScopeFromSessionStorage: jest.fn(),
|
||||
discoverScopes: jest.fn(),
|
||||
}));
|
||||
|
||||
|
|
|
|||
|
|
@ -45,6 +45,8 @@ import {
|
|||
clearClientInformationFromSessionStorage,
|
||||
InspectorOAuthClientProvider,
|
||||
saveClientInformationToSessionStorage,
|
||||
saveScopeToSessionStorage,
|
||||
clearScopeFromSessionStorage,
|
||||
discoverScopes,
|
||||
} from "../auth";
|
||||
import {
|
||||
|
|
@ -144,6 +146,15 @@ export function useConnection({
|
|||
});
|
||||
}, [oauthClientId, oauthClientSecret, sseUrl]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!oauthScope) {
|
||||
clearScopeFromSessionStorage(sseUrl);
|
||||
return;
|
||||
}
|
||||
|
||||
saveScopeToSessionStorage(sseUrl, oauthScope);
|
||||
}, [oauthScope, sseUrl]);
|
||||
|
||||
const pushHistory = (request: object, response?: object) => {
|
||||
setRequestHistory((prev) => [
|
||||
...prev,
|
||||
|
|
@ -366,10 +377,9 @@ export function useConnection({
|
|||
}
|
||||
scope = await discoverScopes(sseUrl, resourceMetadata);
|
||||
}
|
||||
const serverAuthProvider = new InspectorOAuthClientProvider(
|
||||
sseUrl,
|
||||
scope,
|
||||
);
|
||||
|
||||
saveScopeToSessionStorage(sseUrl, scope);
|
||||
const serverAuthProvider = new InspectorOAuthClientProvider(sseUrl);
|
||||
|
||||
const result = await auth(serverAuthProvider, {
|
||||
serverUrl: sseUrl,
|
||||
|
|
|
|||
|
|
@ -80,13 +80,16 @@ export const oauthTransitions: Record<OAuthStep, StateTransition> = {
|
|||
const metadata = context.state.oauthMetadata!;
|
||||
const clientMetadata = context.provider.clientMetadata;
|
||||
|
||||
// Prefer scopes from resource metadata if available
|
||||
const scopesSupported =
|
||||
context.state.resourceMetadata?.scopes_supported ||
|
||||
metadata.scopes_supported;
|
||||
// Add all supported scopes to client registration
|
||||
if (scopesSupported) {
|
||||
clientMetadata.scope = scopesSupported.join(" ");
|
||||
// Priority: user-provided scope > discovered scopes
|
||||
if (!context.provider.scope || context.provider.scope.trim() === "") {
|
||||
// Prefer scopes from resource metadata if available
|
||||
const scopesSupported =
|
||||
context.state.resourceMetadata?.scopes_supported ||
|
||||
metadata.scopes_supported;
|
||||
// Add all supported scopes to client registration
|
||||
if (scopesSupported) {
|
||||
clientMetadata.scope = scopesSupported.join(" ");
|
||||
}
|
||||
}
|
||||
|
||||
// Try Static client first, with DCR as fallback
|
||||
|
|
@ -113,10 +116,14 @@ export const oauthTransitions: Record<OAuthStep, StateTransition> = {
|
|||
const metadata = context.state.oauthMetadata!;
|
||||
const clientInformation = context.state.oauthClientInfo!;
|
||||
|
||||
const scope = await discoverScopes(
|
||||
context.serverUrl,
|
||||
context.state.resourceMetadata ?? undefined,
|
||||
);
|
||||
// Priority: user-provided scope > discovered scopes
|
||||
let scope = context.provider.scope;
|
||||
if (!scope || scope.trim() === "") {
|
||||
scope = await discoverScopes(
|
||||
context.serverUrl,
|
||||
context.state.resourceMetadata ?? undefined,
|
||||
);
|
||||
}
|
||||
|
||||
const { authorizationUrl, codeVerifier } = await startAuthorization(
|
||||
context.serverUrl,
|
||||
|
|
|
|||
|
|
@ -85,8 +85,9 @@ export function tryParseJson(str: string): {
|
|||
success: boolean;
|
||||
data: JsonValue;
|
||||
} {
|
||||
const trimmed = str.trim();
|
||||
const trimmed = str?.trim();
|
||||
if (
|
||||
trimmed &&
|
||||
!(trimmed.startsWith("{") && trimmed.endsWith("}")) &&
|
||||
!(trimmed.startsWith("[") && trimmed.endsWith("]"))
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -235,6 +235,16 @@ export function normalizeUnionType(schema: JsonSchemaType): JsonSchemaType {
|
|||
return { ...schema, type: "integer", anyOf: undefined, nullable: true };
|
||||
}
|
||||
|
||||
// Handle anyOf with exactly array and null (FastMCP pattern)
|
||||
if (
|
||||
schema.anyOf &&
|
||||
schema.anyOf.length === 2 &&
|
||||
schema.anyOf.some((t) => (t as JsonSchemaType).type === "array") &&
|
||||
schema.anyOf.some((t) => (t as JsonSchemaType).type === "null")
|
||||
) {
|
||||
return { ...schema, type: "array", anyOf: undefined, nullable: true };
|
||||
}
|
||||
|
||||
// Handle array type with exactly string and null
|
||||
if (
|
||||
Array.isArray(schema.type) &&
|
||||
|
|
|
|||
Loading…
Reference in a new issue