Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 16 additions & 4 deletions cli/src/client/prompts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,14 @@ type JsonValue =
| { [key: string]: JsonValue };

// List available prompts
export async function listPrompts(client: Client): Promise<McpResponse> {
export async function listPrompts(
client: Client,
metaData?: Record<string, string>,
): Promise<McpResponse> {
try {
const response = await client.listPrompts();
const params =
metaData && Object.keys(metaData).length > 0 ? { _meta: metaData } : {};
const response = await client.listPrompts(params);
return response;
} catch (error) {
throw new Error(
Expand All @@ -28,6 +33,7 @@ export async function getPrompt(
client: Client,
name: string,
args?: Record<string, JsonValue>,
metaData?: Record<string, string>,
): Promise<McpResponse> {
try {
// Convert all arguments to strings for prompt arguments
Expand All @@ -44,10 +50,16 @@ export async function getPrompt(
}
}

const response = await client.getPrompt({
const params: any = {
name,
arguments: stringArgs,
});
};

if (metaData && Object.keys(metaData).length > 0) {
params._meta = metaData;
}

const response = await client.getPrompt(params);

return response;
} catch (error) {
Expand Down
21 changes: 17 additions & 4 deletions cli/src/client/resources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,14 @@ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { McpResponse } from "./types.js";

// List available resources
export async function listResources(client: Client): Promise<McpResponse> {
export async function listResources(
client: Client,
metaData?: Record<string, string>,
): Promise<McpResponse> {
try {
const response = await client.listResources();
const params =
metaData && Object.keys(metaData).length > 0 ? { _meta: metaData } : {};
const response = await client.listResources(params);
return response;
} catch (error) {
throw new Error(
Expand All @@ -17,9 +22,14 @@ export async function listResources(client: Client): Promise<McpResponse> {
export async function readResource(
client: Client,
uri: string,
metaData?: Record<string, string>,
): Promise<McpResponse> {
try {
const response = await client.readResource({ uri });
const params: any = { uri };
if (metaData && Object.keys(metaData).length > 0) {
params._meta = metaData;
}
const response = await client.readResource(params);
return response;
} catch (error) {
throw new Error(
Expand All @@ -31,9 +41,12 @@ export async function readResource(
// List resource templates
export async function listResourceTemplates(
client: Client,
metaData?: Record<string, string>,
): Promise<McpResponse> {
try {
const response = await client.listResourceTemplates();
const params =
metaData && Object.keys(metaData).length > 0 ? { _meta: metaData } : {};
const response = await client.listResourceTemplates(params);
return response;
} catch (error) {
throw new Error(
Expand Down
27 changes: 24 additions & 3 deletions cli/src/client/tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,14 @@ type JsonSchemaType = {
items?: JsonSchemaType;
};

export async function listTools(client: Client): Promise<McpResponse> {
export async function listTools(
client: Client,
metaData?: Record<string, string>,
): Promise<McpResponse> {
try {
const response = await client.listTools();
const params =
metaData && Object.keys(metaData).length > 0 ? { _meta: metaData } : {};
const response = await client.listTools(params);
return response;
} catch (error) {
throw new Error(
Expand Down Expand Up @@ -82,9 +87,11 @@ export async function callTool(
client: Client,
name: string,
args: Record<string, JsonValue>,
generalMetaData?: Record<string, string>,
toolSpecificMetaData?: Record<string, string>,
): Promise<McpResponse> {
try {
const toolsResponse = await listTools(client);
const toolsResponse = await listTools(client, generalMetaData);
const tools = toolsResponse.tools as Tool[];
const tool = tools.find((t) => t.name === name);

Expand All @@ -106,9 +113,23 @@ export async function callTool(
}
}

// Merge general metadata with tool-specific metadata
// Tool-specific metadata takes precedence over general metadata
let mergedMeta: Record<string, string> | undefined;
if (generalMetaData || toolSpecificMetaData) {
mergedMeta = {
...(generalMetaData || {}),
...(toolSpecificMetaData || {}),
};
}

const response = await client.callTool({
name: name,
arguments: convertedArgs,
_meta:
mergedMeta && Object.keys(mergedMeta).length > 0
? mergedMeta
: undefined,
});
return response;
} catch (error) {
Expand Down
45 changes: 38 additions & 7 deletions cli/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,10 @@ type Args = {
logLevel?: LogLevel;
toolName?: string;
toolArg?: Record<string, JsonValue>;
toolMeta?: Record<string, string>;
transport?: "sse" | "stdio" | "http";
headers?: Record<string, string>;
metaData?: Record<string, string>;
};

function createTransportOptions(
Expand Down Expand Up @@ -121,41 +123,52 @@ async function callMethod(args: Args): Promise<void> {

// Tools methods
if (args.method === "tools/list") {
result = await listTools(client);
result = await listTools(client, args.metaData);
} else if (args.method === "tools/call") {
if (!args.toolName) {
throw new Error(
"Tool name is required for tools/call method. Use --tool-name to specify the tool name.",
);
}

result = await callTool(client, args.toolName, args.toolArg || {});
result = await callTool(
client,
args.toolName,
args.toolArg || {},
args.metaData,
args.toolMeta,
);
}
// Resources methods
else if (args.method === "resources/list") {
result = await listResources(client);
result = await listResources(client, args.metaData);
} else if (args.method === "resources/read") {
if (!args.uri) {
throw new Error(
"URI is required for resources/read method. Use --uri to specify the resource URI.",
);
}

result = await readResource(client, args.uri);
result = await readResource(client, args.uri, args.metaData);
} else if (args.method === "resources/templates/list") {
result = await listResourceTemplates(client);
result = await listResourceTemplates(client, args.metaData);
}
// Prompts methods
else if (args.method === "prompts/list") {
result = await listPrompts(client);
result = await listPrompts(client, args.metaData);
} else if (args.method === "prompts/get") {
if (!args.promptName) {
throw new Error(
"Prompt name is required for prompts/get method. Use --prompt-name to specify the prompt name.",
);
}

result = await getPrompt(client, args.promptName, args.promptArgs || {});
result = await getPrompt(
client,
args.promptName,
args.promptArgs || {},
args.metaData,
);
}
// Logging methods
else if (args.method === "logging/setLevel") {
Expand Down Expand Up @@ -327,6 +340,8 @@ function parseArgs(): Args {

const options = program.opts() as Omit<Args, "target"> & {
header?: Record<string, string>;
meta?: Record<string, JsonValue>;
toolMeta?: Record<string, JsonValue>;
};

let remainingArgs = program.args;
Expand All @@ -344,6 +359,22 @@ function parseArgs(): Args {
target: finalArgs,
...options,
headers: options.header, // commander.js uses 'header' field, map to 'headers'
metaData: options.meta
? Object.fromEntries(
Object.entries(options.meta).map(([key, value]) => [
key,
String(value),
]),
)
: undefined,
toolMeta: options.toolMeta
? Object.fromEntries(
Object.entries(options.toolMeta).map(([key, value]) => [
key,
String(value),
]),
)
: undefined,
};
}

Expand Down
55 changes: 49 additions & 6 deletions client/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import {
Hash,
Key,
MessageSquare,
Settings,
} from "lucide-react";

import { z } from "zod";
Expand Down Expand Up @@ -80,6 +81,7 @@ import {
CustomHeaders,
migrateFromLegacyAuth,
} from "./lib/types/customHeaders";
import MetaDataTab from "./components/MetaDataTab";

const CONFIG_LOCAL_STORAGE_KEY = "inspectorConfig_v1";

Expand Down Expand Up @@ -195,9 +197,27 @@ const App = () => {
const [authState, setAuthState] =
useState<AuthDebuggerState>(EMPTY_DEBUGGER_STATE);

// Meta data state - persisted in localStorage
const [metaData, setMetaData] = useState<Record<string, string>>(() => {
const savedMetaData = localStorage.getItem("lastMetaData");
if (savedMetaData) {
try {
return JSON.parse(savedMetaData);
} catch (error) {
console.warn("Failed to parse saved meta data:", error);
}
}
return {};
});

const updateAuthState = (updates: Partial<AuthDebuggerState>) => {
setAuthState((prev) => ({ ...prev, ...updates }));
};

const handleMetaDataChange = (newMetaData: Record<string, string>) => {
setMetaData(newMetaData);
localStorage.setItem("lastMetaData", JSON.stringify(newMetaData));
};
const nextRequestId = useRef(0);
const rootsRef = useRef<Root[]>([]);

Expand Down Expand Up @@ -299,6 +319,7 @@ const App = () => {
},
getRoots: () => rootsRef.current,
defaultLoggingLevel: logLevel,
metaData,
});

useEffect(() => {
Expand Down Expand Up @@ -775,7 +796,11 @@ const App = () => {
cacheToolOutputSchemas(response.tools);
};

const callTool = async (name: string, params: Record<string, unknown>) => {
const callTool = async (
name: string,
params: Record<string, unknown>,
meta?: Record<string, unknown>,
) => {
lastToolCallOriginTabRef.current = currentTabRef.current;

try {
Expand All @@ -785,15 +810,21 @@ const App = () => {
? cleanParams(params, tool.inputSchema as JsonSchemaType)
: params;

// Merge general metadata with tool-specific metadata
// Tool-specific metadata takes precedence over general metadata
const mergedMeta = {
...metaData, // General metadata first
progressToken: progressTokenRef.current++,
...(meta ?? {}), // Tool-specific metadata overrides
};

const response = await sendMCPRequest(
{
method: "tools/call" as const,
params: {
name,
arguments: cleanedParams,
_meta: {
progressToken: progressTokenRef.current++,
},
_meta: mergedMeta,
},
},
CompatibilityCallToolResultSchema,
Expand Down Expand Up @@ -989,6 +1020,10 @@ const App = () => {
<Key className="w-4 h-4 mr-2" />
Auth
</TabsTrigger>
<TabsTrigger value="metadata">
<Settings className="w-4 h-4 mr-2" />
Meta Data
</TabsTrigger>
</TabsList>

<div className="w-full">
Expand Down Expand Up @@ -1099,10 +1134,14 @@ const App = () => {
setNextToolCursor(undefined);
cacheToolOutputSchemas([]);
}}
callTool={async (name, params) => {
callTool={async (
name: string,
params: Record<string, unknown>,
meta?: Record<string, unknown>,
) => {
clearError("tools");
setToolResult(null);
await callTool(name, params);
await callTool(name, params, meta);
}}
selectedTool={selectedTool}
setSelectedTool={(tool) => {
Expand Down Expand Up @@ -1145,6 +1184,10 @@ const App = () => {
onRootsChange={handleRootsChange}
/>
<AuthDebuggerWrapper />
<MetaDataTab
metaData={metaData}
onMetaDataChange={handleMetaDataChange}
/>
</>
)}
</div>
Expand Down
Loading
Loading