mirror of
https://github.com/taskgenius/taskgenius-plugin.git
synced 2026-07-22 06:40:25 +00:00
feat(mcp): add server logging system with modal viewer
- Add comprehensive logging for all MCP tool calls with timestamps, duration, arguments, results, and session tracking - Implement McpLogModal component for viewing logs in settings UI - Add log truncation for large responses (max 500 chars preview) - Add getServer() method to McpServerManager for log access - Fix metadata type safety by checking string type before trim() - Normalize project names to handle arrays and non-string values
This commit is contained in:
parent
1464780e2d
commit
586e8613db
15 changed files with 1098 additions and 353 deletions
|
|
@ -8,6 +8,7 @@ import TaskProgressBarPlugin from "@/index";
|
|||
import { McpServerManager } from "@/mcp/McpServerManager";
|
||||
import { AuthMiddleware } from "@/mcp/auth/AuthMiddleware";
|
||||
import { ConfirmModal } from "@/components/ui/modals/ConfirmModal";
|
||||
import { McpLogModal } from "@/components/ui/modals/McpLogModal";
|
||||
import "@/styles/mcp-integration.css";
|
||||
import { TaskProgressBarSettingTab } from "@/setting";
|
||||
|
||||
|
|
@ -521,6 +522,26 @@ export function renderMcpIntegrationSettingsTab(
|
|||
});
|
||||
});
|
||||
|
||||
// View Logs Button
|
||||
new Setting(actionsContainer)
|
||||
.setName(t("View Server Logs"))
|
||||
.setDesc(t("View all MCP server tool calls and their results"))
|
||||
.addButton((button) => {
|
||||
button
|
||||
.setButtonText(t("View Logs"))
|
||||
.setIcon("file-text")
|
||||
.onClick(() => {
|
||||
const server = plugin.mcpServerManager?.getServer();
|
||||
if (server) {
|
||||
const logs = server.getLogs();
|
||||
const modal = new McpLogModal(plugin, logs);
|
||||
modal.open();
|
||||
} else {
|
||||
new Notice(t("MCP server is not running"));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Client Configuration
|
||||
containerEl.createEl("h3", { text: t("Client Configuration") });
|
||||
|
||||
|
|
|
|||
|
|
@ -1295,7 +1295,7 @@ export class TableRenderer extends Component {
|
|||
if (
|
||||
isProjectColumn &&
|
||||
row?.task?.metadata?.tgProject &&
|
||||
(!row.task.metadata.project || !row.task.metadata.project.trim())
|
||||
(!row.task.metadata.project || typeof row.task.metadata.project !== 'string' || !row.task.metadata.project.trim())
|
||||
) {
|
||||
const tgProject = row.task.metadata.tgProject;
|
||||
const indicator = cellEl.createDiv({
|
||||
|
|
@ -1574,12 +1574,12 @@ export class TableRenderer extends Component {
|
|||
});
|
||||
break;
|
||||
case "project":
|
||||
if (task.metadata.project && task.metadata.project.trim()) {
|
||||
if (task.metadata.project && typeof task.metadata.project === 'string' && task.metadata.project.trim()) {
|
||||
values.add(task.metadata.project);
|
||||
}
|
||||
break;
|
||||
case "context":
|
||||
if (task.metadata.context && task.metadata.context.trim()) {
|
||||
if (task.metadata.context && typeof task.metadata.context === 'string' && task.metadata.context.trim()) {
|
||||
values.add(task.metadata.context);
|
||||
}
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -409,7 +409,7 @@ export class TaskMetadataEditor extends Component {
|
|||
if (
|
||||
isReadonly &&
|
||||
this.task.metadata.tgProject &&
|
||||
(!this.task.metadata.project || !this.task.metadata.project.trim())
|
||||
(!this.task.metadata.project || typeof this.task.metadata.project !== 'string' || !this.task.metadata.project.trim())
|
||||
) {
|
||||
fieldContainer.addClass("project-readonly");
|
||||
const indicator = fieldContainer.createDiv({
|
||||
|
|
|
|||
|
|
@ -349,7 +349,7 @@ export class InlineEditor extends Component {
|
|||
// Add visual indicator for tgProject - only show if no user-set project exists
|
||||
if (
|
||||
this.task.metadata.tgProject &&
|
||||
(!this.task.metadata.project || !this.task.metadata.project.trim())
|
||||
(!this.task.metadata.project || typeof this.task.metadata.project !== 'string' || !this.task.metadata.project.trim())
|
||||
) {
|
||||
const tgProject = this.task.metadata.tgProject;
|
||||
const indicator = container.createDiv({
|
||||
|
|
|
|||
|
|
@ -363,7 +363,7 @@ export class TaskDetailsComponent extends Component {
|
|||
// Add visual indicator for tgProject - only show if no user-set project exists
|
||||
if (
|
||||
task.metadata.tgProject &&
|
||||
(!task.metadata.project || !task.metadata.project.trim())
|
||||
(!task.metadata.project || typeof task.metadata.project !== 'string' || !task.metadata.project.trim())
|
||||
) {
|
||||
const tgProject = task.metadata.tgProject;
|
||||
const indicator = projectField.createDiv({
|
||||
|
|
|
|||
237
src/components/ui/modals/McpLogModal.ts
Normal file
237
src/components/ui/modals/McpLogModal.ts
Normal file
|
|
@ -0,0 +1,237 @@
|
|||
import { Modal, ButtonComponent, ExtraButtonComponent } from "obsidian";
|
||||
import TaskProgressBarPlugin from "@/index";
|
||||
import { McpLogEntry } from "@/mcp/McpServer";
|
||||
import "@/styles/modal.css";
|
||||
import { formatDate } from "@/utils/date/date-formatter";
|
||||
|
||||
/**
|
||||
* Modal for displaying MCP server logs
|
||||
*/
|
||||
export class McpLogModal extends Modal {
|
||||
private plugin: TaskProgressBarPlugin;
|
||||
private logs: McpLogEntry[];
|
||||
private logsContainerEl: HTMLElement;
|
||||
private searchInput: HTMLInputElement;
|
||||
private filteredLogs: McpLogEntry[];
|
||||
|
||||
constructor(plugin: TaskProgressBarPlugin, logs: McpLogEntry[]) {
|
||||
super(plugin.app);
|
||||
this.plugin = plugin;
|
||||
this.logs = logs;
|
||||
this.filteredLogs = logs;
|
||||
}
|
||||
|
||||
onOpen() {
|
||||
this.titleEl.setText("MCP Server Logs");
|
||||
this.modalEl.addClass("mcp-log-modal");
|
||||
|
||||
// Header section with search and clear
|
||||
const headerEl = this.contentEl.createDiv({ cls: "mcp-log-header" });
|
||||
|
||||
// Search input
|
||||
const searchContainer = headerEl.createDiv({
|
||||
cls: "mcp-log-search-container",
|
||||
});
|
||||
this.searchInput = searchContainer.createEl("input", {
|
||||
type: "text",
|
||||
placeholder: "Search logs...",
|
||||
cls: "mcp-log-search-input",
|
||||
});
|
||||
this.searchInput.addEventListener("input", () => this.filterLogs());
|
||||
|
||||
// Clear logs button
|
||||
new ButtonComponent(headerEl)
|
||||
.setButtonText("Clear Logs")
|
||||
.setIcon("trash")
|
||||
.setWarning()
|
||||
.onClick(() => {
|
||||
if (this.plugin.mcpServerManager?.getServer()) {
|
||||
this.plugin.mcpServerManager.getServer()?.clearLogs();
|
||||
this.logs = [];
|
||||
this.filteredLogs = [];
|
||||
this.renderLogs();
|
||||
}
|
||||
});
|
||||
|
||||
// Stats section
|
||||
const statsEl = this.contentEl.createDiv({ cls: "mcp-log-stats" });
|
||||
this.renderStats(statsEl);
|
||||
|
||||
// Logs container with scrolling
|
||||
this.logsContainerEl = this.contentEl.createDiv({
|
||||
cls: "mcp-log-container",
|
||||
});
|
||||
this.renderLogs();
|
||||
|
||||
// Footer buttons
|
||||
const footerEl = this.contentEl.createDiv({
|
||||
cls: "modal-button-container",
|
||||
});
|
||||
|
||||
new ButtonComponent(footerEl).setButtonText("Refresh").onClick(() => {
|
||||
if (this.plugin.mcpServerManager?.getServer()) {
|
||||
this.logs =
|
||||
this.plugin.mcpServerManager.getServer()?.getLogs() || [];
|
||||
this.filterLogs();
|
||||
}
|
||||
});
|
||||
|
||||
new ButtonComponent(footerEl).setButtonText("Close").onClick(() => {
|
||||
this.close();
|
||||
});
|
||||
}
|
||||
|
||||
private filterLogs() {
|
||||
const query = this.searchInput.value.toLowerCase();
|
||||
if (!query) {
|
||||
this.filteredLogs = this.logs;
|
||||
} else {
|
||||
this.filteredLogs = this.logs.filter((log) => {
|
||||
return (
|
||||
log.toolName?.toLowerCase().includes(query) ||
|
||||
log.method.toLowerCase().includes(query) ||
|
||||
log.error?.toLowerCase().includes(query) ||
|
||||
JSON.stringify(log.arguments).toLowerCase().includes(query)
|
||||
);
|
||||
});
|
||||
}
|
||||
this.renderLogs();
|
||||
}
|
||||
|
||||
private renderStats(container: HTMLElement) {
|
||||
container.empty();
|
||||
|
||||
const totalLogs = this.logs.length;
|
||||
const errorCount = this.logs.filter((log) => log.error).length;
|
||||
const successCount = totalLogs - errorCount;
|
||||
|
||||
const statsText = container.createDiv({ cls: "mcp-log-stats-text" });
|
||||
statsText.setText(
|
||||
`Total: ${totalLogs} | Success: ${successCount} | Errors: ${errorCount}`
|
||||
);
|
||||
}
|
||||
|
||||
private renderLogs() {
|
||||
this.logsContainerEl.empty();
|
||||
|
||||
if (this.filteredLogs.length === 0) {
|
||||
const emptyEl = this.logsContainerEl.createDiv({
|
||||
cls: "mcp-log-empty",
|
||||
});
|
||||
emptyEl.createEl("p", {
|
||||
text:
|
||||
this.logs.length === 0
|
||||
? "No logs yet. Tool calls will appear here."
|
||||
: "No logs match your search.",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.filteredLogs.forEach((log, index) => {
|
||||
const logEntry = this.logsContainerEl.createDiv({
|
||||
cls: `mcp-log-entry ${
|
||||
log.error ? "mcp-log-error" : "mcp-log-success"
|
||||
}`,
|
||||
});
|
||||
|
||||
// Header row
|
||||
const headerRow = logEntry.createDiv({
|
||||
cls: "mcp-log-entry-header",
|
||||
});
|
||||
|
||||
// Timestamp
|
||||
const timestamp = headerRow.createDiv({
|
||||
cls: "mcp-log-timestamp",
|
||||
});
|
||||
timestamp.setText(formatDate(log.timestamp));
|
||||
|
||||
// Tool name or method
|
||||
const toolName = headerRow.createDiv({ cls: "mcp-log-tool-name" });
|
||||
toolName.setText(log.toolName || log.method);
|
||||
|
||||
// Duration
|
||||
const duration = headerRow.createDiv({ cls: "mcp-log-duration" });
|
||||
duration.setText(`${log.duration}ms`);
|
||||
|
||||
// Session ID (if available)
|
||||
if (log.sessionId) {
|
||||
const sessionId = headerRow.createDiv({
|
||||
cls: "mcp-log-session-id",
|
||||
});
|
||||
sessionId.setText(log.sessionId.substring(0, 12) + "...");
|
||||
sessionId.setAttribute("title", log.sessionId);
|
||||
}
|
||||
|
||||
let isExpanded: boolean = false;
|
||||
// Toggle button for details
|
||||
new ExtraButtonComponent(headerRow)
|
||||
.setIcon("chevron-down")
|
||||
.onClick(() => {
|
||||
isExpanded = !isExpanded;
|
||||
detailsEl.toggle(isExpanded);
|
||||
});
|
||||
|
||||
// Details section (collapsed by default)
|
||||
const detailsEl = logEntry.createDiv({
|
||||
cls: "mcp-log-details",
|
||||
});
|
||||
detailsEl.style.display = "none";
|
||||
|
||||
// Arguments
|
||||
if (log.arguments && Object.keys(log.arguments).length > 0) {
|
||||
const argsSection = detailsEl.createDiv({
|
||||
cls: "mcp-log-section",
|
||||
});
|
||||
argsSection.createEl("div", {
|
||||
cls: "mcp-log-section-title",
|
||||
text: "Arguments:",
|
||||
});
|
||||
const argsContent = argsSection.createEl("pre", {
|
||||
cls: "mcp-log-section-content",
|
||||
});
|
||||
argsContent.setText(JSON.stringify(log.arguments, null, 2));
|
||||
}
|
||||
|
||||
// Result or Error
|
||||
if (log.error) {
|
||||
const errorSection = detailsEl.createDiv({
|
||||
cls: "mcp-log-section",
|
||||
});
|
||||
errorSection.createEl("div", {
|
||||
cls: "mcp-log-section-title mcp-log-error-title",
|
||||
text: "Error:",
|
||||
});
|
||||
const errorContent = errorSection.createEl("pre", {
|
||||
cls: "mcp-log-section-content mcp-log-error-content",
|
||||
});
|
||||
errorContent.setText(log.error);
|
||||
} else if (log.result) {
|
||||
const resultSection = detailsEl.createDiv({
|
||||
cls: "mcp-log-section",
|
||||
});
|
||||
const resultTitle = resultSection.createEl("div", {
|
||||
cls: "mcp-log-section-title",
|
||||
text: "Result:",
|
||||
});
|
||||
|
||||
if (log.truncated) {
|
||||
resultTitle.createEl("span", {
|
||||
cls: "mcp-log-truncated-badge",
|
||||
text: " (truncated)",
|
||||
});
|
||||
}
|
||||
|
||||
const resultContent = resultSection.createEl("pre", {
|
||||
cls: "mcp-log-section-content",
|
||||
});
|
||||
resultContent.setText(JSON.stringify(log.result, null, 2));
|
||||
}
|
||||
|
||||
// Toggle functionality
|
||||
});
|
||||
}
|
||||
|
||||
onClose() {
|
||||
this.contentEl.empty();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,2 +1,3 @@
|
|||
export * from './ConfirmModal';
|
||||
export * from './IframeModal';
|
||||
export * from './IframeModal';
|
||||
export * from './McpLogModal';
|
||||
|
|
@ -450,12 +450,21 @@ export class Augmentor {
|
|||
? String(ctx.fileMeta?.[metadataKeyFromConfig]).trim()
|
||||
: undefined;
|
||||
|
||||
const effectiveProjectName =
|
||||
let effectiveProjectName =
|
||||
ctx.projectName ||
|
||||
projectFromMeta ||
|
||||
projectFromMetadataKey ||
|
||||
projectFromFrontmatter;
|
||||
|
||||
// Normalize effectiveProjectName to string: handle arrays (take first element), non-strings, etc.
|
||||
if (effectiveProjectName) {
|
||||
if (Array.isArray(effectiveProjectName)) {
|
||||
effectiveProjectName = effectiveProjectName[0]?.toString() || undefined;
|
||||
} else if (typeof effectiveProjectName !== 'string') {
|
||||
effectiveProjectName = String(effectiveProjectName);
|
||||
}
|
||||
}
|
||||
|
||||
// Set project name if not already set
|
||||
if (!metadata.project && effectiveProjectName) {
|
||||
metadata.project = effectiveProjectName;
|
||||
|
|
|
|||
|
|
@ -16,6 +16,21 @@ const http = require("http");
|
|||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const url = require("url");
|
||||
|
||||
/**
|
||||
* Log entry for MCP tool calls
|
||||
*/
|
||||
export interface McpLogEntry {
|
||||
timestamp: Date;
|
||||
sessionId?: string;
|
||||
method: string;
|
||||
toolName?: string;
|
||||
arguments: any;
|
||||
result: any;
|
||||
error?: string;
|
||||
duration: number;
|
||||
truncated?: boolean;
|
||||
}
|
||||
|
||||
export class McpServer {
|
||||
private httpServer: any;
|
||||
private authMiddleware: AuthMiddleware;
|
||||
|
|
@ -27,6 +42,8 @@ export class McpServer {
|
|||
private actualPort?: number;
|
||||
private sessions: Map<string, { created: Date; lastAccess: Date }> =
|
||||
new Map();
|
||||
private logs: McpLogEntry[] = [];
|
||||
private readonly MAX_LOGS = 1000;
|
||||
|
||||
constructor(
|
||||
private plugin: TaskProgressBarPlugin,
|
||||
|
|
@ -62,6 +79,57 @@ export class McpServer {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Truncate large data for logging
|
||||
*/
|
||||
private truncateForLog(data: any, maxLength: number = 500): any {
|
||||
const str = JSON.stringify(data);
|
||||
if (str.length <= maxLength) {
|
||||
return data;
|
||||
}
|
||||
|
||||
// Check if it's an array of tasks
|
||||
if (Array.isArray(data) && data.length > 0) {
|
||||
return {
|
||||
_truncated: true,
|
||||
_type: "array",
|
||||
_count: data.length,
|
||||
_sample: data.slice(0, 2), // Show first 2 items
|
||||
};
|
||||
}
|
||||
|
||||
// For other objects, just truncate the string
|
||||
return {
|
||||
_truncated: true,
|
||||
_preview: str.substring(0, maxLength) + "...",
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a log entry
|
||||
*/
|
||||
private addLog(entry: McpLogEntry): void {
|
||||
this.logs.unshift(entry); // Add to beginning
|
||||
// Keep only MAX_LOGS entries
|
||||
if (this.logs.length > this.MAX_LOGS) {
|
||||
this.logs = this.logs.slice(0, this.MAX_LOGS);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all logs (most recent first)
|
||||
*/
|
||||
public getLogs(): McpLogEntry[] {
|
||||
return [...this.logs];
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all logs
|
||||
*/
|
||||
public clearLogs(): void {
|
||||
this.logs = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get prompt definitions
|
||||
*/
|
||||
|
|
@ -853,11 +921,17 @@ export class McpServer {
|
|||
/**
|
||||
* Execute a tool
|
||||
*/
|
||||
private async executeTool(toolName: string, args: any): Promise<any> {
|
||||
private async executeTool(
|
||||
toolName: string,
|
||||
args: any,
|
||||
sessionId?: string
|
||||
): Promise<any> {
|
||||
const startTime = Date.now();
|
||||
const taskBridge = await this.requireTaskBridge();
|
||||
try {
|
||||
let result: any;
|
||||
let result: any;
|
||||
let error: string | undefined;
|
||||
|
||||
try {
|
||||
switch (toolName) {
|
||||
case "query_tasks":
|
||||
result = await taskBridge.queryTasks(args);
|
||||
|
|
@ -950,6 +1024,19 @@ export class McpServer {
|
|||
throw new Error(`Tool not found: ${toolName}`);
|
||||
}
|
||||
|
||||
// Log successful execution
|
||||
const duration = Date.now() - startTime;
|
||||
this.addLog({
|
||||
timestamp: new Date(),
|
||||
sessionId,
|
||||
method: "tools/call",
|
||||
toolName,
|
||||
arguments: args,
|
||||
result: this.truncateForLog(result),
|
||||
duration,
|
||||
truncated: JSON.stringify(result).length > 500,
|
||||
});
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
|
|
@ -958,17 +1045,32 @@ export class McpServer {
|
|||
},
|
||||
],
|
||||
};
|
||||
} catch (error: any) {
|
||||
} catch (err: any) {
|
||||
error = err.message;
|
||||
const duration = Date.now() - startTime;
|
||||
|
||||
// Log failed execution
|
||||
this.addLog({
|
||||
timestamp: new Date(),
|
||||
sessionId,
|
||||
method: "tools/call",
|
||||
toolName,
|
||||
arguments: args,
|
||||
result: null,
|
||||
error,
|
||||
duration,
|
||||
});
|
||||
|
||||
// Re-throw tool not found errors so they can be handled properly
|
||||
if (error.message.includes("Tool not found")) {
|
||||
throw error;
|
||||
if (err.message.includes("Tool not found")) {
|
||||
throw err;
|
||||
}
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: JSON.stringify(
|
||||
{ success: false, error: error.message },
|
||||
{ success: false, error: err.message },
|
||||
null,
|
||||
2
|
||||
),
|
||||
|
|
@ -1095,7 +1197,8 @@ export class McpServer {
|
|||
try {
|
||||
const result = await this.executeTool(
|
||||
toolName,
|
||||
toolArgs
|
||||
toolArgs,
|
||||
sessionId
|
||||
);
|
||||
return {
|
||||
jsonrpc: "2.0",
|
||||
|
|
@ -1305,6 +1408,11 @@ export class McpServer {
|
|||
});
|
||||
|
||||
req.on("end", async () => {
|
||||
// Debug: log received body for UTF-8 validation
|
||||
if (body.includes("content")) {
|
||||
console.log("[MCP Debug] Received body:", body.substring(0, 300));
|
||||
}
|
||||
|
||||
let request;
|
||||
try {
|
||||
request = JSON.parse(body);
|
||||
|
|
|
|||
|
|
@ -234,7 +234,7 @@ export class McpServerManager {
|
|||
regenerateAuthToken(): string {
|
||||
const newToken = AuthMiddleware.generateToken();
|
||||
this.config.authToken = newToken;
|
||||
|
||||
|
||||
if (this.server) {
|
||||
this.server.updateConfig({ authToken: newToken });
|
||||
}
|
||||
|
|
@ -243,6 +243,10 @@ export class McpServerManager {
|
|||
return newToken;
|
||||
}
|
||||
|
||||
getServer(): McpServer | undefined {
|
||||
return this.server;
|
||||
}
|
||||
|
||||
async cleanup(): Promise<void> {
|
||||
await this.stop();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,537 +2,571 @@
|
|||
|
||||
/* Status Container */
|
||||
.mcp-status-container {
|
||||
padding: 16px;
|
||||
background: var(--background-secondary);
|
||||
border-radius: 8px;
|
||||
margin-bottom: 24px;
|
||||
border: 1px solid var(--background-modifier-border);
|
||||
padding: 16px;
|
||||
background: var(--background-secondary);
|
||||
border-radius: 8px;
|
||||
margin-bottom: 24px;
|
||||
border: 1px solid var(--background-modifier-border);
|
||||
}
|
||||
|
||||
.mcp-status {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.mcp-status-indicator {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
font-weight: 600;
|
||||
font-size: 1.05em;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
font-weight: 600;
|
||||
font-size: 1.05em;
|
||||
}
|
||||
|
||||
.mcp-status-indicator.running .status-dot {
|
||||
color: var(--background-modifier-success);
|
||||
font-size: 1.2em;
|
||||
animation: pulse 2s infinite;
|
||||
color: var(--background-modifier-success);
|
||||
font-size: 1.2em;
|
||||
animation: pulse 2s infinite;
|
||||
}
|
||||
|
||||
.mcp-status-indicator.stopped .status-dot {
|
||||
color: var(--text-muted);
|
||||
font-size: 1.2em;
|
||||
color: var(--text-muted);
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
.mcp-status-details {
|
||||
display: flex;
|
||||
gap: 24px;
|
||||
font-size: 0.9em;
|
||||
color: var(--text-muted);
|
||||
padding-left: 30px;
|
||||
display: flex;
|
||||
gap: 24px;
|
||||
font-size: 0.9em;
|
||||
color: var(--text-muted);
|
||||
padding-left: 30px;
|
||||
}
|
||||
|
||||
/* Client Configuration */
|
||||
.mcp-config-container {
|
||||
margin-bottom: 30px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.mcp-client-section {
|
||||
margin-bottom: 16px;
|
||||
background: var(--background-primary);
|
||||
border: 1px solid var(--background-modifier-border);
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
margin-bottom: 16px;
|
||||
background: var(--background-primary);
|
||||
border: 1px solid var(--background-modifier-border);
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.mcp-client-header {
|
||||
cursor: pointer;
|
||||
padding: 12px 16px;
|
||||
background: var(--background-secondary);
|
||||
border-radius: 0;
|
||||
margin-bottom: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
user-select: none;
|
||||
transition: background-color 0.2s;
|
||||
cursor: pointer;
|
||||
padding: 12px 16px;
|
||||
background: var(--background-secondary);
|
||||
border-radius: 0;
|
||||
margin-bottom: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
user-select: none;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.mcp-client-header:hover {
|
||||
background: var(--background-modifier-hover);
|
||||
background: var(--background-modifier-hover);
|
||||
}
|
||||
|
||||
/* Arrow icon for collapsible sections */
|
||||
.mcp-arrow {
|
||||
transition: transform 0.2s;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
transition: transform 0.2s;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.mcp-arrow svg {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
stroke-width: 2;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
stroke-width: 2;
|
||||
}
|
||||
|
||||
.mcp-arrow.expanded {
|
||||
transform: rotate(90deg);
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.mcp-client-name {
|
||||
font-weight: 500;
|
||||
color: var(--text-normal);
|
||||
font-weight: 500;
|
||||
color: var(--text-normal);
|
||||
}
|
||||
|
||||
.mcp-client-content {
|
||||
padding: 16px !important;
|
||||
background: var(--background-primary);
|
||||
display: none;
|
||||
padding: 16px !important;
|
||||
background: var(--background-primary);
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mcp-client-content.expanded {
|
||||
display: block;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.mcp-config-block {
|
||||
position: relative;
|
||||
margin-bottom: 20px;
|
||||
position: relative;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.mcp-config-code {
|
||||
position: relative;
|
||||
padding: 16px 60px 16px 16px !important;
|
||||
background: var(--code-background);
|
||||
border: 1px solid var(--background-modifier-border);
|
||||
border-radius: 6px;
|
||||
margin-bottom: 0;
|
||||
font-size: 0.85em;
|
||||
line-height: 1.5;
|
||||
overflow: auto;
|
||||
max-height: 300px;
|
||||
font-family: var(--font-monospace);
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: var(--scrollbar-thumb-bg) var(--scrollbar-bg);
|
||||
position: relative;
|
||||
padding: 16px 60px 16px 16px !important;
|
||||
background: var(--code-background);
|
||||
border: 1px solid var(--background-modifier-border);
|
||||
border-radius: 6px;
|
||||
margin-bottom: 0;
|
||||
font-size: 0.85em;
|
||||
line-height: 1.5;
|
||||
overflow: auto;
|
||||
max-height: 300px;
|
||||
font-family: var(--font-monospace);
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: var(--scrollbar-thumb-bg) var(--scrollbar-bg);
|
||||
}
|
||||
|
||||
.mcp-config-code code {
|
||||
color: var(--code-normal);
|
||||
font-family: var(--font-monospace);
|
||||
white-space: pre;
|
||||
display: block;
|
||||
color: var(--code-normal);
|
||||
font-family: var(--font-monospace);
|
||||
white-space: pre;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.mcp-config-code::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
.mcp-config-code::-webkit-scrollbar-track {
|
||||
background: var(--scrollbar-bg);
|
||||
background: var(--scrollbar-bg);
|
||||
}
|
||||
|
||||
.mcp-config-code::-webkit-scrollbar-thumb {
|
||||
background: var(--scrollbar-thumb-bg);
|
||||
border-radius: 4px;
|
||||
background: var(--scrollbar-thumb-bg);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.mcp-config-code::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--scrollbar-active-thumb-bg);
|
||||
background: var(--scrollbar-active-thumb-bg);
|
||||
}
|
||||
|
||||
/* Copy Buttons */
|
||||
.mcp-copy-btn {
|
||||
position: absolute !important;
|
||||
top: 12px;
|
||||
right: 12px;
|
||||
padding: 6px 12px;
|
||||
font-size: 0.8em;
|
||||
background: var(--interactive-accent);
|
||||
color: var(--text-on-accent);
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
font-weight: 500;
|
||||
z-index: 10;
|
||||
position: absolute !important;
|
||||
top: 12px;
|
||||
right: 12px;
|
||||
padding: 6px 12px;
|
||||
font-size: 0.8em;
|
||||
background: var(--interactive-accent);
|
||||
color: var(--text-on-accent);
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
font-weight: 500;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.mcp-copy-btn:hover {
|
||||
background: var(--interactive-accent-hover);
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
||||
background: var(--interactive-accent-hover);
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.mcp-copy-btn.copied {
|
||||
animation: copySuccess 0.3s ease;
|
||||
background: var(--background-modifier-success) !important;
|
||||
animation: copySuccess 0.3s ease;
|
||||
background: var(--background-modifier-success) !important;
|
||||
}
|
||||
|
||||
/* Authentication Methods List */
|
||||
.mcp-auth-methods {
|
||||
margin: 15px 0;
|
||||
padding: 15px;
|
||||
background: var(--background-secondary);
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--background-modifier-border);
|
||||
margin: 15px 0;
|
||||
padding: 15px;
|
||||
background: var(--background-secondary);
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--background-modifier-border);
|
||||
}
|
||||
|
||||
.mcp-auth-list {
|
||||
margin: 10px 0 0 20px;
|
||||
line-height: 1.8;
|
||||
margin: 10px 0 0 20px;
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
.mcp-auth-list li {
|
||||
margin: 8px 0;
|
||||
color: var(--text-normal);
|
||||
margin: 8px 0;
|
||||
color: var(--text-normal);
|
||||
}
|
||||
|
||||
.mcp-auth-list code {
|
||||
background: var(--code-background);
|
||||
padding: 2px 6px;
|
||||
border-radius: 3px;
|
||||
font-size: 0.9em;
|
||||
color: var(--code-normal);
|
||||
background: var(--code-background);
|
||||
padding: 2px 6px;
|
||||
border-radius: 3px;
|
||||
font-size: 0.9em;
|
||||
color: var(--code-normal);
|
||||
}
|
||||
|
||||
/* Token Input */
|
||||
.mcp-token-input {
|
||||
font-family: monospace;
|
||||
width: 300px;
|
||||
margin-right: 10px;
|
||||
font-family: monospace;
|
||||
width: 300px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
/* API Documentation */
|
||||
.mcp-docs-container {
|
||||
margin-top: 20px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.mcp-docs-section {
|
||||
margin-bottom: 30px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.mcp-docs-subtitle {
|
||||
font-size: 1em;
|
||||
font-weight: 600;
|
||||
margin-bottom: 12px;
|
||||
color: var(--text-normal);
|
||||
font-size: 1em;
|
||||
font-weight: 600;
|
||||
margin-bottom: 12px;
|
||||
color: var(--text-normal);
|
||||
}
|
||||
|
||||
/* Endpoint Box */
|
||||
.mcp-endpoint-box {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 12px;
|
||||
background: var(--background-secondary);
|
||||
border: 1px solid var(--background-modifier-border);
|
||||
border-radius: 8px;
|
||||
margin-bottom: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 12px;
|
||||
background: var(--background-secondary);
|
||||
border: 1px solid var(--background-modifier-border);
|
||||
border-radius: 8px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.mcp-endpoint-content {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.mcp-endpoint-label {
|
||||
font-weight: 500;
|
||||
color: var(--text-muted);
|
||||
font-weight: 500;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.mcp-endpoint-url {
|
||||
font-family: var(--font-monospace);
|
||||
padding: 6px 10px;
|
||||
background: var(--background-primary);
|
||||
border: 1px solid var(--background-modifier-border);
|
||||
border-radius: 4px;
|
||||
color: var(--text-accent);
|
||||
user-select: text;
|
||||
font-family: var(--font-monospace);
|
||||
padding: 6px 10px;
|
||||
background: var(--background-primary);
|
||||
border: 1px solid var(--background-modifier-border);
|
||||
border-radius: 4px;
|
||||
color: var(--text-accent);
|
||||
user-select: text;
|
||||
}
|
||||
|
||||
.mcp-copy-endpoint-btn {
|
||||
padding: 6px 12px;
|
||||
background: var(--interactive-normal);
|
||||
color: var(--text-normal);
|
||||
border: 1px solid var(--background-modifier-border);
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s;
|
||||
button.mcp-copy-endpoint-btn {
|
||||
padding: 6px 12px;
|
||||
background: var(--interactive-normal);
|
||||
color: var(--text-normal);
|
||||
border: 1px solid var(--background-modifier-border);
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.mcp-copy-endpoint-btn:hover {
|
||||
background: var(--interactive-hover);
|
||||
button.mcp-copy-endpoint-btn:hover {
|
||||
background: var(--interactive-hover);
|
||||
}
|
||||
|
||||
.mcp-copy-endpoint-btn.copied {
|
||||
animation: copySuccess 0.3s ease;
|
||||
background: var(--background-modifier-success) !important;
|
||||
button.mcp-copy-endpoint-btn.copied {
|
||||
animation: copySuccess 0.3s ease;
|
||||
background: var(--background-modifier-success) !important;
|
||||
}
|
||||
|
||||
/* Tools Grid */
|
||||
.mcp-tools-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||
gap: 16px;
|
||||
margin-bottom: 24px;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||
gap: 16px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.mcp-tool-card {
|
||||
position: relative;
|
||||
padding: 16px;
|
||||
background: linear-gradient(135deg, var(--background-secondary) 0%, var(--background-primary) 100%);
|
||||
border: 1px solid var(--background-modifier-border);
|
||||
border-radius: 10px;
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
padding: 16px;
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
var(--background-secondary) 0%,
|
||||
var(--background-primary) 100%
|
||||
);
|
||||
border: 1px solid var(--background-modifier-border);
|
||||
border-radius: 10px;
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.mcp-tool-card::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 3px;
|
||||
background: linear-gradient(90deg, var(--interactive-accent) 0%, var(--text-accent) 100%);
|
||||
transform: scaleX(0);
|
||||
transform-origin: left;
|
||||
transition: transform 0.3s ease;
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 3px;
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
var(--interactive-accent) 0%,
|
||||
var(--text-accent) 100%
|
||||
);
|
||||
transform: scaleX(0);
|
||||
transform-origin: left;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.mcp-tool-card:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
|
||||
border-color: var(--interactive-accent);
|
||||
transform: translateY(-4px);
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
|
||||
border-color: var(--interactive-accent);
|
||||
}
|
||||
|
||||
.mcp-tool-card:hover::before {
|
||||
transform: scaleX(1);
|
||||
transform: scaleX(1);
|
||||
}
|
||||
|
||||
.mcp-tool-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.mcp-tool-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--interactive-accent);
|
||||
opacity: 0.9;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--interactive-accent);
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.mcp-tool-icon svg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
stroke-width: 2;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
stroke-width: 2;
|
||||
}
|
||||
|
||||
.mcp-tool-name {
|
||||
font-family: var(--font-monospace);
|
||||
font-size: 0.85em;
|
||||
font-weight: 600;
|
||||
color: var(--text-accent);
|
||||
background: var(--background-modifier-box);
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
display: inline-block;
|
||||
letter-spacing: 0.5px;
|
||||
font-family: var(--font-monospace);
|
||||
font-size: 0.85em;
|
||||
font-weight: 600;
|
||||
color: var(--text-accent);
|
||||
background: var(--background-modifier-box);
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
display: inline-block;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.mcp-tool-desc {
|
||||
font-size: 0.85em;
|
||||
color: var(--text-muted);
|
||||
line-height: 1.5;
|
||||
padding-left: 28px;
|
||||
font-size: 0.85em;
|
||||
color: var(--text-muted);
|
||||
line-height: 1.5;
|
||||
padding-left: 28px;
|
||||
}
|
||||
|
||||
/* Example Container */
|
||||
.mcp-example-container {
|
||||
background: var(--background-primary);
|
||||
border: 1px solid var(--background-modifier-border);
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
|
||||
margin-top: 16px;
|
||||
background: var(--background-primary);
|
||||
border: 1px solid var(--background-modifier-border);
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
/* Example Tabs */
|
||||
.mcp-example-tabs {
|
||||
display: flex;
|
||||
gap: 0;
|
||||
background: linear-gradient(180deg, var(--background-secondary) 0%, var(--background-modifier-box) 100%);
|
||||
border-bottom: 1px solid var(--background-modifier-border);
|
||||
padding: 0;
|
||||
display: flex;
|
||||
gap: 0;
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
var(--background-secondary) 0%,
|
||||
var(--background-modifier-box) 100%
|
||||
);
|
||||
border-bottom: 1px solid var(--background-modifier-border);
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.mcp-example-tab {
|
||||
flex: 1;
|
||||
padding: 14px 20px;
|
||||
background: transparent;
|
||||
color: var(--text-muted);
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
font-size: 0.9em;
|
||||
font-weight: 500;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
box-shadow: none !important;
|
||||
flex: 1;
|
||||
padding: 14px 20px;
|
||||
background: transparent;
|
||||
color: var(--text-muted);
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
font-size: 0.9em;
|
||||
font-weight: 500;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
.mcp-example-tab::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%) scaleX(0);
|
||||
width: 80%;
|
||||
height: 3px;
|
||||
background: var(--interactive-accent);
|
||||
transition: transform 0.3s ease;
|
||||
border-radius: 3px 3px 0 0;
|
||||
content: "";
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%) scaleX(0);
|
||||
width: 80%;
|
||||
height: 3px;
|
||||
background: var(--interactive-accent);
|
||||
transition: transform 0.3s ease;
|
||||
border-radius: 3px 3px 0 0;
|
||||
}
|
||||
|
||||
.mcp-example-tab:hover {
|
||||
background: rgba(var(--interactive-accent-rgb), 0.05);
|
||||
color: var(--text-normal);
|
||||
background: rgba(var(--interactive-accent-rgb), 0.05);
|
||||
color: var(--text-normal);
|
||||
}
|
||||
|
||||
.mcp-example-tab.active {
|
||||
background: rgba(var(--interactive-accent-rgb), 0.1);
|
||||
color: var(--interactive-accent);
|
||||
font-weight: 600;
|
||||
background: rgba(var(--interactive-accent-rgb), 0.1);
|
||||
color: var(--interactive-accent);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.mcp-example-tab.active::after {
|
||||
transform: translateX(-50%) scaleX(1);
|
||||
transform: translateX(-50%) scaleX(1);
|
||||
}
|
||||
|
||||
/* Example Code */
|
||||
.mcp-example-code-container {
|
||||
position: relative;
|
||||
background: var(--code-background);
|
||||
border-top: 1px solid var(--background-modifier-border);
|
||||
position: relative;
|
||||
background: var(--code-background);
|
||||
border-top: 1px solid var(--background-modifier-border);
|
||||
}
|
||||
|
||||
.mcp-example-block {
|
||||
display: none;
|
||||
position: relative;
|
||||
animation: fadeIn 0.3s ease;
|
||||
display: none;
|
||||
position: relative;
|
||||
animation: fadeIn 0.3s ease;
|
||||
}
|
||||
|
||||
.mcp-example-block.active {
|
||||
display: block;
|
||||
display: block;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(-10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(-10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.mcp-example-code {
|
||||
margin: 0;
|
||||
padding: 24px 80px 24px 24px;
|
||||
background: linear-gradient(135deg, var(--code-background) 0%, rgba(var(--background-primary-rgb), 0.5) 100%);
|
||||
font-size: 0.85em;
|
||||
line-height: 1.7;
|
||||
overflow-x: auto;
|
||||
max-height: 500px;
|
||||
font-family: var(--font-monospace);
|
||||
border: none;
|
||||
position: relative;
|
||||
margin: 0;
|
||||
padding: 24px 80px 24px 24px;
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
var(--code-background) 0%,
|
||||
rgba(var(--background-primary-rgb), 0.5) 100%
|
||||
);
|
||||
font-size: 0.85em;
|
||||
line-height: 1.7;
|
||||
overflow-x: auto;
|
||||
max-height: 500px;
|
||||
font-family: var(--font-monospace);
|
||||
border: none;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.mcp-example-code::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 1px;
|
||||
background: linear-gradient(90deg, transparent, var(--background-modifier-border), transparent);
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 1px;
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
transparent,
|
||||
var(--background-modifier-border),
|
||||
transparent
|
||||
);
|
||||
}
|
||||
|
||||
.mcp-example-code code {
|
||||
color: var(--code-normal);
|
||||
font-family: var(--font-monospace);
|
||||
white-space: pre;
|
||||
display: block;
|
||||
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||
color: var(--code-normal);
|
||||
font-family: var(--font-monospace);
|
||||
white-space: pre;
|
||||
display: block;
|
||||
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.mcp-example-copy-btn {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
padding: 8px 16px;
|
||||
background: var(--interactive-accent);
|
||||
color: var(--text-on-accent);
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-size: 0.85em;
|
||||
font-weight: 600;
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
z-index: 10;
|
||||
box-shadow: 0 2px 8px rgba(var(--interactive-accent-rgb), 0.3);
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
padding: 8px 16px;
|
||||
background: var(--interactive-accent);
|
||||
color: var(--text-on-accent);
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-size: 0.85em;
|
||||
font-weight: 600;
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
z-index: 10;
|
||||
box-shadow: 0 2px 8px rgba(var(--interactive-accent-rgb), 0.3);
|
||||
}
|
||||
|
||||
.mcp-example-copy-btn:hover {
|
||||
background: var(--interactive-accent-hover);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(var(--interactive-accent-rgb), 0.4);
|
||||
background: var(--interactive-accent-hover);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(var(--interactive-accent-rgb), 0.4);
|
||||
}
|
||||
|
||||
.mcp-example-copy-btn:active {
|
||||
transform: translateY(0);
|
||||
box-shadow: 0 1px 4px rgba(var(--interactive-accent-rgb), 0.3);
|
||||
transform: translateY(0);
|
||||
box-shadow: 0 1px 4px rgba(var(--interactive-accent-rgb), 0.3);
|
||||
}
|
||||
|
||||
.mcp-example-copy-btn.copied {
|
||||
animation: copySuccess 0.4s ease;
|
||||
background: var(--background-modifier-success) !important;
|
||||
box-shadow: 0 2px 8px rgba(var(--interactive-success-rgb), 0.3);
|
||||
animation: copySuccess 0.4s ease;
|
||||
background: var(--background-modifier-success) !important;
|
||||
box-shadow: 0 2px 8px rgba(var(--interactive-success-rgb), 0.3);
|
||||
}
|
||||
|
||||
/* Animations */
|
||||
@keyframes copySuccess {
|
||||
0% { transform: scale(1); }
|
||||
50% { transform: scale(1.1); }
|
||||
100% { transform: scale(1); }
|
||||
0% {
|
||||
transform: scale(1);
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
100% {
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0% { opacity: 1; }
|
||||
50% { opacity: 0.6; }
|
||||
100% { opacity: 1; }
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
opacity: 0.6;
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Smooth transitions for all interactive elements */
|
||||
|
|
@ -540,40 +574,40 @@
|
|||
.mcp-example-tab,
|
||||
.mcp-tool-card,
|
||||
button {
|
||||
transition: all 0.2s ease;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
/* Code syntax colors for better readability */
|
||||
.mcp-config-code .json-key {
|
||||
color: var(--text-accent);
|
||||
color: var(--text-accent);
|
||||
}
|
||||
|
||||
.mcp-config-code .json-string {
|
||||
color: var(--text-success);
|
||||
color: var(--text-success);
|
||||
}
|
||||
|
||||
.mcp-config-code .json-number {
|
||||
color: var(--text-warning);
|
||||
color: var(--text-warning);
|
||||
}
|
||||
|
||||
.mcp-config-code .json-boolean {
|
||||
color: var(--text-error);
|
||||
color: var(--text-error);
|
||||
}
|
||||
|
||||
/* Responsive adjustments */
|
||||
@media (max-width: 768px) {
|
||||
.mcp-tools-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.mcp-status-details {
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
.mcp-tools-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.mcp-status-details {
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.mcp-example-subtitle {
|
||||
padding-left: var(--size-4-4);
|
||||
padding-left: var(--size-4-4);
|
||||
}
|
||||
|
||||
/* Cursor One-Click Install Section */
|
||||
|
|
@ -607,7 +641,6 @@ button {
|
|||
gap: 10px;
|
||||
}
|
||||
|
||||
|
||||
.mcp-cursor-copy-deeplink-btn {
|
||||
background: var(--interactive-normal);
|
||||
color: var(--text-normal);
|
||||
|
|
@ -685,4 +718,4 @@ button {
|
|||
margin: 24px 0;
|
||||
border: none;
|
||||
border-top: 1px solid var(--background-modifier-border);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -126,3 +126,182 @@
|
|||
color: var(--text-muted);
|
||||
font-size: var(--font-ui-small);
|
||||
}
|
||||
|
||||
/* MCP Log Modal Styles */
|
||||
.mcp-log-modal {
|
||||
min-width: 700px;
|
||||
max-width: 900px;
|
||||
}
|
||||
|
||||
.mcp-log-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--size-4-3);
|
||||
margin-bottom: var(--size-4-3);
|
||||
padding-bottom: var(--size-4-3);
|
||||
border-bottom: 1px solid var(--background-modifier-border);
|
||||
}
|
||||
|
||||
.mcp-log-search-container {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.mcp-log-search-input {
|
||||
width: 100%;
|
||||
padding: var(--size-2-2) var(--size-4-2);
|
||||
border: 1px solid var(--background-modifier-border);
|
||||
border-radius: var(--radius-s);
|
||||
background: var(--background-primary);
|
||||
color: var(--text-normal);
|
||||
font-size: var(--font-ui-small);
|
||||
}
|
||||
|
||||
.mcp-log-search-input:focus {
|
||||
outline: none;
|
||||
border-color: var(--interactive-accent);
|
||||
}
|
||||
|
||||
.mcp-log-stats {
|
||||
margin-bottom: var(--size-4-3);
|
||||
padding: var(--size-2-3);
|
||||
background: var(--background-secondary);
|
||||
border-radius: var(--radius-s);
|
||||
}
|
||||
|
||||
.mcp-log-stats-text {
|
||||
font-size: var(--font-ui-small);
|
||||
color: var(--text-muted);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.mcp-log-container {
|
||||
max-height: 500px;
|
||||
overflow-y: auto;
|
||||
padding: var(--size-2-2);
|
||||
border: 1px solid var(--background-modifier-border);
|
||||
border-radius: var(--radius-s);
|
||||
background: var(--background-primary);
|
||||
}
|
||||
|
||||
.mcp-log-empty {
|
||||
text-align: center;
|
||||
padding: var(--size-4-8);
|
||||
color: var(--text-muted);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.mcp-log-entry {
|
||||
margin-bottom: var(--size-2-3);
|
||||
padding: var(--size-2-3);
|
||||
border: 1px solid var(--background-modifier-border);
|
||||
border-radius: var(--radius-s);
|
||||
background: var(--background-secondary);
|
||||
}
|
||||
|
||||
.mcp-log-entry:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.mcp-log-error {
|
||||
background: var(--background-modifier-error);
|
||||
}
|
||||
|
||||
.mcp-log-entry-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--size-4-2);
|
||||
font-size: var(--font-ui-small);
|
||||
}
|
||||
|
||||
.mcp-log-timestamp {
|
||||
color: var(--text-muted);
|
||||
font-family: var(--font-monospace);
|
||||
font-size: var(--font-smallest);
|
||||
min-width: 80px;
|
||||
}
|
||||
|
||||
.mcp-log-tool-name {
|
||||
font-weight: 500;
|
||||
color: var(--text-normal);
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.mcp-log-duration {
|
||||
color: var(--text-muted);
|
||||
font-family: var(--font-monospace);
|
||||
font-size: var(--font-smallest);
|
||||
}
|
||||
|
||||
.mcp-log-session-id {
|
||||
color: var(--text-faint);
|
||||
font-family: var(--font-monospace);
|
||||
font-size: var(--font-smallest);
|
||||
cursor: help;
|
||||
}
|
||||
|
||||
.mcp-log-toggle-btn {
|
||||
padding: 2px 6px;
|
||||
border: none;
|
||||
background: var(--interactive-normal);
|
||||
color: var(--text-muted);
|
||||
border-radius: var(--radius-s);
|
||||
cursor: pointer;
|
||||
font-size: var(--font-smallest);
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.mcp-log-toggle-btn:hover {
|
||||
background: var(--interactive-hover);
|
||||
}
|
||||
|
||||
.mcp-log-details {
|
||||
margin-top: var(--size-2-3);
|
||||
padding-top: var(--size-2-3);
|
||||
border-top: 1px solid var(--background-modifier-border);
|
||||
}
|
||||
|
||||
.mcp-log-section {
|
||||
margin-bottom: var(--size-2-3);
|
||||
}
|
||||
|
||||
.mcp-log-section:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.mcp-log-section-title {
|
||||
font-size: var(--font-ui-smaller);
|
||||
font-weight: 600;
|
||||
color: var(--text-normal);
|
||||
margin-bottom: var(--size-2-1);
|
||||
}
|
||||
|
||||
.mcp-log-error-title {
|
||||
color: var(--text-error);
|
||||
}
|
||||
|
||||
.mcp-log-section-content {
|
||||
font-family: var(--font-monospace);
|
||||
font-size: var(--font-smallest);
|
||||
background: var(--background-primary);
|
||||
border: 1px solid var(--background-modifier-border);
|
||||
border-radius: var(--radius-s);
|
||||
padding: var(--size-2-2);
|
||||
margin: 0;
|
||||
overflow-x: auto;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.mcp-log-error-content {
|
||||
color: var(--text-error);
|
||||
background: var(--background-modifier-error);
|
||||
}
|
||||
|
||||
.mcp-log-truncated-badge {
|
||||
font-size: var(--font-smallest);
|
||||
color: var(--text-muted);
|
||||
font-weight: normal;
|
||||
font-style: italic;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -643,7 +643,8 @@ export function filterTasks(
|
|||
if (filterRules.project) {
|
||||
filtered = filtered.filter(
|
||||
(task) =>
|
||||
task.metadata.project?.trim() === filterRules.project?.trim(),
|
||||
typeof task.metadata.project === 'string' && typeof filterRules.project === 'string' &&
|
||||
task.metadata.project.trim() === filterRules.project.trim(),
|
||||
);
|
||||
}
|
||||
if (filterRules.priority !== undefined) {
|
||||
|
|
|
|||
|
|
@ -579,7 +579,7 @@ export function getEffectiveProject(task: Task): string | undefined {
|
|||
}
|
||||
|
||||
// Check original project - must be non-empty and not just whitespace
|
||||
if (task.metadata.project && task.metadata.project.trim()) {
|
||||
if (task.metadata.project && typeof task.metadata.project === 'string' && task.metadata.project.trim()) {
|
||||
return task.metadata.project;
|
||||
}
|
||||
|
||||
|
|
@ -606,7 +606,7 @@ export function isProjectReadonly(task: Task): boolean {
|
|||
}
|
||||
|
||||
// If there's an original project that's not empty/whitespace, it's always editable
|
||||
if (task.metadata.project && task.metadata.project.trim()) {
|
||||
if (task.metadata.project && typeof task.metadata.project === 'string' && task.metadata.project.trim()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -633,7 +633,7 @@ export function hasProject(task: Task): boolean {
|
|||
}
|
||||
|
||||
// Check if original project exists and is not empty/whitespace
|
||||
if (task.metadata.project && task.metadata.project.trim()) {
|
||||
if (task.metadata.project && typeof task.metadata.project === 'string' && task.metadata.project.trim()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
|||
160
styles.css
160
styles.css
File diff suppressed because one or more lines are too long
Loading…
Reference in a new issue