MCP Server Plugin (#335)

This commit is contained in:
Gregory Schier
2025-12-31 08:41:57 -08:00
committed by GitHub
parent 58eff84f43
commit 6b9b207e1c
44 changed files with 2144 additions and 191 deletions

View File

@@ -0,0 +1,33 @@
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import * as z from 'zod/v4';
import type { McpServerContext } from '../types.js';
import { getWorkspaceContext } from './helpers.js';
export function registerFolderTools(server: McpServer, ctx: McpServerContext) {
server.registerTool(
'list_folders',
{
title: 'List Folders',
description: 'List all folders in a workspace',
inputSchema: z.object({
workspaceId: z
.string()
.optional()
.describe('Workspace ID (required if multiple workspaces are open)'),
}),
},
async ({ workspaceId }) => {
const workspaceCtx = await getWorkspaceContext(ctx, workspaceId);
const folders = await workspaceCtx.yaak.folder.list();
return {
content: [
{
type: 'text' as const,
text: JSON.stringify(folders, null, 2),
},
],
};
},
);
}

View File

@@ -0,0 +1,32 @@
import type { McpServerContext } from '../types.js';
export async function getWorkspaceContext(
ctx: McpServerContext,
workspaceId?: string,
): Promise<McpServerContext> {
const workspaces = await ctx.yaak.workspace.list();
if (!workspaceId && workspaces.length > 1) {
const workspaceList = workspaces.map((w, i) => `${i + 1}. ${w.name} (ID: ${w.id})`).join('\n');
throw new Error(
`Multiple workspaces are open. Please specify which workspace to use.\n\n` +
`Currently open workspaces:\n${workspaceList}\n\n` +
`You can use the list_workspaces tool to get workspace IDs, then use other tools ` +
`with the workspace context. For example, ask the user which workspace they want ` +
`to work with by presenting them with the numbered list above.`,
);
}
const workspace = workspaceId ? workspaces.find((w) => w.id === workspaceId) : workspaces[0];
if (!workspace) {
const workspaceList = workspaces.map((w) => `- ${w.name} (ID: ${w.id})`).join('\n');
throw new Error(
`Workspace with ID "${workspaceId}" not found.\n\n` +
`Available workspaces:\n${workspaceList}`,
);
}
return {
yaak: ctx.yaak.workspace.withContext(workspace),
};
}

View File

@@ -0,0 +1,219 @@
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import * as z from 'zod/v4';
import type { McpServerContext } from '../types.js';
import { getWorkspaceContext } from './helpers.js';
export function registerHttpRequestTools(server: McpServer, ctx: McpServerContext) {
server.registerTool(
'list_http_requests',
{
title: 'List HTTP Requests',
description: 'List all HTTP requests in a workspace',
inputSchema: z.object({
workspaceId: z
.string()
.optional()
.describe('Workspace ID (required if multiple workspaces are open)'),
}),
},
async ({ workspaceId }) => {
const workspaceCtx = await getWorkspaceContext(ctx, workspaceId);
const requests = await workspaceCtx.yaak.httpRequest.list();
return {
content: [
{
type: 'text' as const,
text: JSON.stringify(requests, null, 2),
},
],
};
},
);
server.registerTool(
'get_http_request',
{
title: 'Get HTTP Request',
description: 'Get details of a specific HTTP request by ID',
inputSchema: z.object({
id: z.string().describe('The HTTP request ID'),
workspaceId: z
.string()
.optional()
.describe('Workspace ID (required if multiple workspaces are open)'),
}),
},
async ({ id, workspaceId }) => {
const workspaceCtx = await getWorkspaceContext(ctx, workspaceId);
const request = await workspaceCtx.yaak.httpRequest.getById({ id });
return {
content: [
{
type: 'text' as const,
text: JSON.stringify(request, null, 2),
},
],
};
},
);
server.registerTool(
'send_http_request',
{
title: 'Send HTTP Request',
description: 'Send an HTTP request and get the response',
inputSchema: z.object({
id: z.string().describe('The HTTP request ID to send'),
environmentId: z.string().optional().describe('Optional environment ID to use'),
workspaceId: z
.string()
.optional()
.describe('Workspace ID (required if multiple workspaces are open)'),
}),
},
async ({ id, workspaceId }) => {
const workspaceCtx = await getWorkspaceContext(ctx, workspaceId);
const httpRequest = await workspaceCtx.yaak.httpRequest.getById({ id });
if (httpRequest == null) {
throw new Error(`HTTP request with ID ${id} not found`);
}
const response = await workspaceCtx.yaak.httpRequest.send({ httpRequest });
return {
content: [
{
type: 'text' as const,
text: JSON.stringify(response, null, 2),
},
],
};
},
);
server.registerTool(
'create_http_request',
{
title: 'Create HTTP Request',
description: 'Create a new HTTP request',
inputSchema: z.object({
workspaceId: z
.string()
.optional()
.describe('Workspace ID (required if multiple workspaces are open)'),
name: z
.string()
.optional()
.describe('Request name (empty string to auto-generate from URL)'),
url: z.string().describe('Request URL'),
method: z.string().optional().describe('HTTP method (defaults to GET)'),
folderId: z.string().optional().describe('Parent folder ID'),
description: z.string().optional().describe('Request description'),
headers: z
.array(
z.object({
name: z.string(),
value: z.string(),
enabled: z.boolean().default(true),
}),
)
.optional()
.describe('Request headers'),
urlParameters: z
.array(
z.object({
name: z.string(),
value: z.string(),
enabled: z.boolean().default(true),
}),
)
.optional()
.describe('URL query parameters'),
}),
},
async ({ workspaceId: ogWorkspaceId, ...args }) => {
const workspaceCtx = await getWorkspaceContext(ctx, ogWorkspaceId);
const workspaceId = await workspaceCtx.yaak.window.workspaceId();
if (!workspaceId) {
throw new Error('No workspace is open');
}
const httpRequest = await workspaceCtx.yaak.httpRequest.create({
workspaceId: workspaceId,
...args,
});
return {
content: [{ type: 'text' as const, text: JSON.stringify(httpRequest, null, 2) }],
};
},
);
server.registerTool(
'update_http_request',
{
title: 'Update HTTP Request',
description: 'Update an existing HTTP request',
inputSchema: z.object({
id: z.string().describe('HTTP request ID to update'),
workspaceId: z
.string()
.optional()
.describe('Workspace ID (required if multiple workspaces are open)'),
name: z.string().optional().describe('Request name'),
url: z.string().optional().describe('Request URL'),
method: z.string().optional().describe('HTTP method'),
folderId: z.string().optional().describe('Parent folder ID'),
description: z.string().optional().describe('Request description'),
headers: z
.array(
z.object({
name: z.string(),
value: z.string(),
enabled: z.boolean().default(true),
}),
)
.optional()
.describe('Request headers'),
urlParameters: z
.array(
z.object({
name: z.string(),
value: z.string(),
enabled: z.boolean().default(true),
}),
)
.optional()
.describe('URL query parameters'),
}),
},
async ({ id, workspaceId, ...updates }) => {
const workspaceCtx = await getWorkspaceContext(ctx, workspaceId);
const httpRequest = await workspaceCtx.yaak.httpRequest.update({ id, ...updates });
return {
content: [{ type: 'text' as const, text: JSON.stringify(httpRequest, null, 2) }],
};
},
);
server.registerTool(
'delete_http_request',
{
title: 'Delete HTTP Request',
description: 'Delete an HTTP request by ID',
inputSchema: z.object({
id: z.string().describe('HTTP request ID to delete'),
}),
},
async ({ id }) => {
const httpRequest = await ctx.yaak.httpRequest.delete({ id });
return {
content: [
{ type: 'text' as const, text: `Deleted: ${httpRequest.name} (${httpRequest.id})` },
],
};
},
);
}

View File

@@ -0,0 +1,59 @@
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import type { Color, Icon } from '@yaakapp/api';
import * as z from 'zod/v4';
import type { McpServerContext } from '../types.js';
const ICON_VALUES = [
'alert_triangle',
'check',
'check_circle',
'chevron_down',
'copy',
'info',
'pin',
'search',
'trash',
] as const satisfies readonly Icon[];
const COLOR_VALUES = [
'primary',
'secondary',
'info',
'success',
'notice',
'warning',
'danger',
] as const satisfies readonly Color[];
export function registerToastTools(server: McpServer, ctx: McpServerContext) {
server.registerTool(
'show_toast',
{
title: 'Show Toast',
description: 'Show a toast notification in Yaak',
inputSchema: z.object({
message: z.string().describe('The message to display'),
icon: z.enum(ICON_VALUES).optional().describe('Icon name'),
color: z.enum(COLOR_VALUES).optional().describe('Toast color'),
timeout: z.number().optional().describe('Timeout in milliseconds'),
}),
},
async ({ message, icon, color, timeout }) => {
await ctx.yaak.toast.show({
message,
icon,
color,
timeout,
});
return {
content: [
{
type: 'text' as const,
text: `✓ Toast shown: "${message}"`,
},
],
};
},
);
}

View File

@@ -0,0 +1,50 @@
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import * as z from 'zod/v4';
import type { McpServerContext } from '../types.js';
import { getWorkspaceContext } from './helpers.js';
export function registerWindowTools(server: McpServer, ctx: McpServerContext) {
server.registerTool(
'get_workspace_id',
{
title: 'Get Workspace ID',
description: 'Get the current workspace ID',
inputSchema: z.object({}),
},
async () => {
const workspaceCtx = await getWorkspaceContext(ctx);
const workspaceId = await workspaceCtx.yaak.window.workspaceId();
return {
content: [
{
type: 'text' as const,
text: workspaceId || 'No workspace open',
},
],
};
},
);
server.registerTool(
'get_environment_id',
{
title: 'Get Environment ID',
description: 'Get the current environment ID',
inputSchema: z.object({}),
},
async () => {
const workspaceCtx = await getWorkspaceContext(ctx);
const environmentId = await workspaceCtx.yaak.window.environmentId();
return {
content: [
{
type: 'text' as const,
text: environmentId || 'No environment selected',
},
],
};
},
);
}

View File

@@ -0,0 +1,26 @@
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import * as z from 'zod/v4';
import type { McpServerContext } from '../types.js';
export function registerWorkspaceTools(server: McpServer, ctx: McpServerContext) {
server.registerTool(
'list_workspaces',
{
title: 'List Workspaces',
description: 'List all open workspaces in Yaak',
inputSchema: z.object({}),
},
async () => {
const workspaces = await ctx.yaak.workspace.list();
return {
content: [
{
type: 'text' as const,
text: JSON.stringify(workspaces, null, 2),
},
],
};
},
);
}