Merge branch 'main' into meta_properies_in_tools

This commit is contained in:
Konstantin Konstantinov 2025-11-15 01:34:22 +02:00 • committed by GitHub
commit a5d5593c97
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 184 additions and 53 deletions

View file

@ -34,3 +34,7 @@ If applicable, add screenshots to help explain your problem.
**Additional context**
Add any other context about the problem here.
**Version Consideration**
Inspector V2 is under development to address architectural and UX improvements. See [CONTRIBUTING.md](CONTRIBUTING.md) for information about V2 development.

57
.github/pull_request_template.md vendored Normal file
View file

@ -0,0 +1,57 @@
## Summary
<!-- Provide a brief description of what this PR does -->
> **Note:** Inspector V2 is under development to address architectural and UX improvements. See [CONTRIBUTING.md](CONTRIBUTING.md) for information about V2 development.
## Type of Change
<!-- Mark the relevant option with an "x" -->
- [ ] Bug fix (non-breaking change that fixes an issue)
- [ ] New feature (non-breaking change that adds functionality)
- [ ] Documentation update
- [ ] Refactoring (no functional changes)
- [ ] Test updates
- [ ] Build/CI improvements
## Changes Made
<!-- Describe the changes in detail. Include screenshots/recordings if applicable -->
## Related Issues
<!-- Link to related issues using #issue_number or "Fixes #issue_number" -->
## Testing
<!-- Describe how you tested these changes, where applicable -->
- [ ] Tested in UI mode
- [ ] Tested in CLI mode
- [ ] Tested with STDIO transport
- [ ] Tested with SSE transport
- [ ] Tested with Streamable HTTP transport
- [ ] Added/updated automated tests
- [ ] Manual testing performed
### Test Results and/or Instructions
<!-- Provide steps for reviewers to test your changes -->
Screenshots are encouraged to share your testing results for this change.
## Checklist
- [ ] Code follows the style guidelines (ran `npm run prettier-fix`)
- [ ] Self-review completed
- [ ] Code is commented where necessary
- [ ] Documentation updated (README, comments, etc.)
## Breaking Changes
<!-- If this is a breaking change, describe the impact and migration path -->
## Additional Context
<!-- Add any other context, screenshots, or information about the PR here -->

View file

@ -8,7 +8,7 @@ on:
jobs:
test:
# Installing Playright dependencies can take quite awhile, and also depends on GitHub CI load.
# Installing Playwright dependencies can take quite awhile, and also depends on GitHub CI load.
timeout-minutes: 15
runs-on: ubuntu-latest

View file

@ -1,5 +1,7 @@
# MCP Inspector Development Guide
> **Note:** Inspector V2 is under development to address architectural and UX improvements. These guidelines apply to the current version (V1). See [CONTRIBUTING.md](CONTRIBUTING.md) for information about V2 development.
## Build Commands
- Build all: `npm run build`

View file

@ -9,6 +9,17 @@ Thanks for your interest in contributing! This guide explains how to get involve
3. Run `npm run dev` to start both client and server in development mode
4. Use the web UI at http://localhost:6274 to interact with the inspector
## Inspector V2 Development
We're actively developing **Inspector V2** to address architectural and UX improvements. We invite you to follow progress and participate in the Inspector V2 Working Group in [Discord](https://modelcontextprotocol.io/community/communication), [weekly meetings](https://meet.modelcontextprotocol.io/tag/inspector-v2-wg), and [GitHub Discussions](https://github.com/modelcontextprotocol/modelcontextprotocol/discussions/categories/meeting-notes-other) (where notes are posted after meetings).
**Current version (V1) contribution scope:**
- Critical issues are actively maintained
- Small UX improvements are welcome
- Documentation updates are always appreciated
- Major changes will be directed to V2 development
## Development Process & Pull Requests
1. Create a new branch for your changes
@ -30,7 +41,7 @@ If you find a security vulnerability, please refer to our [Security Policy](SECU
## Questions?
Feel free to [open an issue](https://github.com/modelcontextprotocol/mcp-inspector/issues) for questions or create a discussion for general topics.
Feel free to [open an issue](https://github.com/modelcontextprotocol/mcp-inspector/issues) for questions or join the MCP Contributor [Discord server](https://modelcontextprotocol.io/community/communication). Also, please see notes above on Inspector V2 Development.
## License

View file

@ -1,6 +1,6 @@
{
"name": "@modelcontextprotocol/inspector-cli",
"version": "0.17.2",
"version": "0.17.3",
"description": "CLI for the Model Context Protocol inspector",
"license": "MIT",
"author": "Anthropic, PBC (https://anthropic.com)",

View file

@ -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)",

View file

@ -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

View file

@ -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,
})
}
/>

View file

@ -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";

View file

@ -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

View file

@ -112,6 +112,8 @@ jest.mock("../../auth", () => ({
})),
clearClientInformationFromSessionStorage: jest.fn(),
saveClientInformationToSessionStorage: jest.fn(),
saveScopeToSessionStorage: jest.fn(),
clearScopeFromSessionStorage: jest.fn(),
discoverScopes: jest.fn(),
}));

View file

@ -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,

View file

@ -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,

View file

@ -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("]"))
) {

View file

@ -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) &&

16
package-lock.json generated
View file

@ -1,12 +1,12 @@
{
"name": "@modelcontextprotocol/inspector",
"version": "0.17.2",
"version": "0.17.3",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@modelcontextprotocol/inspector",
"version": "0.17.2",
"version": "0.17.3",
"license": "MIT",
"workspaces": [
"client",
@ -14,9 +14,9 @@
"cli"
],
"dependencies": {
"@modelcontextprotocol/inspector-cli": "^0.17.2",
"@modelcontextprotocol/inspector-client": "^0.17.2",
"@modelcontextprotocol/inspector-server": "^0.17.2",
"@modelcontextprotocol/inspector-cli": "^0.17.3",
"@modelcontextprotocol/inspector-client": "^0.17.3",
"@modelcontextprotocol/inspector-server": "^0.17.3",
"@modelcontextprotocol/sdk": "^1.20.1",
"concurrently": "^9.2.0",
"node-fetch": "^3.3.2",
@ -48,7 +48,7 @@
},
"cli": {
"name": "@modelcontextprotocol/inspector-cli",
"version": "0.17.2",
"version": "0.17.3",
"license": "MIT",
"dependencies": {
"@modelcontextprotocol/sdk": "^1.20.1",
@ -71,7 +71,7 @@
},
"client": {
"name": "@modelcontextprotocol/inspector-client",
"version": "0.17.2",
"version": "0.17.3",
"license": "MIT",
"dependencies": {
"@modelcontextprotocol/sdk": "^1.20.1",
@ -12028,7 +12028,7 @@
},
"server": {
"name": "@modelcontextprotocol/inspector-server",
"version": "0.17.2",
"version": "0.17.3",
"license": "MIT",
"dependencies": {
"@modelcontextprotocol/sdk": "^1.20.1",

View file

@ -1,6 +1,6 @@
{
"name": "@modelcontextprotocol/inspector",
"version": "0.17.2",
"version": "0.17.3",
"description": "Model Context Protocol inspector",
"license": "MIT",
"author": "Anthropic, PBC (https://anthropic.com)",
@ -47,9 +47,9 @@
"check-version": "node scripts/check-version-consistency.js"
},
"dependencies": {
"@modelcontextprotocol/inspector-cli": "^0.17.2",
"@modelcontextprotocol/inspector-client": "^0.17.2",
"@modelcontextprotocol/inspector-server": "^0.17.2",
"@modelcontextprotocol/inspector-cli": "^0.17.3",
"@modelcontextprotocol/inspector-client": "^0.17.3",
"@modelcontextprotocol/inspector-server": "^0.17.3",
"@modelcontextprotocol/sdk": "^1.20.1",
"concurrently": "^9.2.0",
"node-fetch": "^3.3.2",

View file

@ -1,6 +1,6 @@
{
"name": "@modelcontextprotocol/inspector-server",
"version": "0.17.2",
"version": "0.17.3",
"description": "Server-side application for the Model Context Protocol inspector",
"license": "MIT",
"author": "Anthropic, PBC (https://anthropic.com)",