mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-03-25 19:01:38 +01:00
Merge main into proxy branch (formatting and docs)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -50,7 +50,7 @@ This will generate a random JSON body on every request:
|
||||
The plugin provides access to all FakerJS modules and their methods:
|
||||
|
||||
| Category | Description | Example Methods |
|
||||
|------------|---------------------------|--------------------------------------------|
|
||||
| ---------- | ------------------------- | ------------------------------------------ |
|
||||
| `airline` | Airline-related data | `aircraftType`, `airline`, `airplane` |
|
||||
| `animal` | Animal names and types | `bear`, `bird`, `cat`, `dog`, `fish` |
|
||||
| `color` | Colors in various formats | `human`, `rgb`, `hex`, `hsl` |
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"name": "@yaak/faker",
|
||||
"private": true,
|
||||
"version": "1.1.1",
|
||||
"displayName": "Faker",
|
||||
"version": "1.1.1",
|
||||
"private": true,
|
||||
"description": "Template functions for generating fake data using FakerJS",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -12,7 +12,7 @@
|
||||
"scripts": {
|
||||
"build": "yaakcli build",
|
||||
"dev": "yaakcli dev",
|
||||
"test": "vitest --run tests"
|
||||
"test": "vp test --run tests"
|
||||
},
|
||||
"dependencies": {
|
||||
"@faker-js/faker": "^10.1.0"
|
||||
|
||||
@@ -1,34 +1,34 @@
|
||||
import { faker } from '@faker-js/faker';
|
||||
import type { DynamicTemplateFunctionArg, PluginDefinition } from '@yaakapp/api';
|
||||
import { faker } from "@faker-js/faker";
|
||||
import type { DynamicTemplateFunctionArg, PluginDefinition } from "@yaakapp/api";
|
||||
|
||||
const modules = [
|
||||
'airline',
|
||||
'animal',
|
||||
'color',
|
||||
'commerce',
|
||||
'company',
|
||||
'database',
|
||||
'date',
|
||||
'finance',
|
||||
'git',
|
||||
'hacker',
|
||||
'image',
|
||||
'internet',
|
||||
'location',
|
||||
'lorem',
|
||||
'person',
|
||||
'music',
|
||||
'number',
|
||||
'phone',
|
||||
'science',
|
||||
'string',
|
||||
'system',
|
||||
'vehicle',
|
||||
'word',
|
||||
"airline",
|
||||
"animal",
|
||||
"color",
|
||||
"commerce",
|
||||
"company",
|
||||
"database",
|
||||
"date",
|
||||
"finance",
|
||||
"git",
|
||||
"hacker",
|
||||
"image",
|
||||
"internet",
|
||||
"location",
|
||||
"lorem",
|
||||
"person",
|
||||
"music",
|
||||
"number",
|
||||
"phone",
|
||||
"science",
|
||||
"string",
|
||||
"system",
|
||||
"vehicle",
|
||||
"word",
|
||||
];
|
||||
|
||||
function normalizeResult(result: unknown): string {
|
||||
if (typeof result === 'string') return result;
|
||||
if (typeof result === "string") return result;
|
||||
if (result instanceof Date) return result.toISOString();
|
||||
return JSON.stringify(result);
|
||||
}
|
||||
@@ -37,20 +37,20 @@ function normalizeResult(result: unknown): string {
|
||||
function args(modName: string, fnName: string): DynamicTemplateFunctionArg[] {
|
||||
return [
|
||||
{
|
||||
type: 'banner',
|
||||
color: 'info',
|
||||
type: "banner",
|
||||
color: "info",
|
||||
inputs: [
|
||||
{
|
||||
type: 'markdown',
|
||||
type: "markdown",
|
||||
content: `Need help? View documentation for [\`${modName}.${fnName}(…)\`](https://fakerjs.dev/api/${encodeURIComponent(modName)}.html#${encodeURIComponent(fnName)})`,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'options',
|
||||
label: 'Arguments',
|
||||
type: 'editor',
|
||||
language: 'json',
|
||||
name: "options",
|
||||
label: "Arguments",
|
||||
type: "editor",
|
||||
language: "json",
|
||||
optional: true,
|
||||
placeholder: 'e.g. { "min": 1, "max": 10 } or 10 or ["en","US"]',
|
||||
},
|
||||
@@ -61,22 +61,22 @@ export const plugin: PluginDefinition = {
|
||||
templateFunctions: modules.flatMap((modName) => {
|
||||
const mod = faker[modName as keyof typeof faker];
|
||||
return Object.keys(mod)
|
||||
.filter((n) => n !== 'faker')
|
||||
.filter((n) => n !== "faker")
|
||||
.map((fnName) => ({
|
||||
name: ['faker', modName, fnName].join('.'),
|
||||
name: ["faker", modName, fnName].join("."),
|
||||
args: args(modName, fnName),
|
||||
async onRender(_ctx, args) {
|
||||
const fn = mod[fnName as keyof typeof mod] as (...a: unknown[]) => unknown;
|
||||
const options = args.values.options;
|
||||
|
||||
// No options supplied
|
||||
if (options == null || options === '') {
|
||||
if (options == null || options === "") {
|
||||
return normalizeResult(fn());
|
||||
}
|
||||
|
||||
// Try JSON first
|
||||
let parsed: unknown = options;
|
||||
if (typeof options === 'string') {
|
||||
if (typeof options === "string") {
|
||||
try {
|
||||
parsed = JSON.parse(options);
|
||||
} catch {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { describe, expect, it } from "vite-plus/test";
|
||||
|
||||
describe('template-function-faker', () => {
|
||||
it('exports all expected template functions', async () => {
|
||||
const { plugin } = await import('../src/index');
|
||||
describe("template-function-faker", () => {
|
||||
it("exports all expected template functions", async () => {
|
||||
const { plugin } = await import("../src/index");
|
||||
const names = plugin.templateFunctions?.map((fn) => fn.name).sort() ?? [];
|
||||
|
||||
// Snapshot the full list of exported function names so we catch any
|
||||
@@ -10,12 +10,13 @@ describe('template-function-faker', () => {
|
||||
expect(names).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('renders date results as unquoted ISO strings', async () => {
|
||||
const { plugin } = await import('../src/index');
|
||||
const fn = plugin.templateFunctions?.find((fn) => fn.name === 'faker.date.future');
|
||||
it("renders date results as unquoted ISO strings", async () => {
|
||||
const { plugin } = await import("../src/index");
|
||||
const fn = plugin.templateFunctions?.find((fn) => fn.name === "faker.date.future");
|
||||
// oxlint-disable-next-line unbound-method
|
||||
const onRender = fn?.onRender;
|
||||
|
||||
expect(onRender).toBeTypeOf('function');
|
||||
expect(onRender).toBeTypeOf("function");
|
||||
if (onRender == null) {
|
||||
throw new Error("Expected template function 'faker.date.future' to define onRender");
|
||||
}
|
||||
|
||||
@@ -16,26 +16,26 @@ remembered for next time.
|
||||
|
||||
Each language supports one or more libraries:
|
||||
|
||||
| Language | Libraries |
|
||||
|---|---|
|
||||
| C | libcurl |
|
||||
| Clojure | clj-http |
|
||||
| C# | HttpClient, RestSharp |
|
||||
| Go | Native |
|
||||
| HTTP | HTTP/1.1 |
|
||||
| Java | AsyncHttp, NetHttp, OkHttp, Unirest |
|
||||
| JavaScript | Axios, fetch, jQuery, XHR |
|
||||
| Kotlin | OkHttp |
|
||||
| Node.js | Axios, fetch, HTTP, Request, Unirest |
|
||||
| Objective-C | NSURLSession |
|
||||
| OCaml | CoHTTP |
|
||||
| PHP | cURL, Guzzle, HTTP v1, HTTP v2 |
|
||||
| PowerShell | Invoke-WebRequest, RestMethod |
|
||||
| Python | http.client, Requests |
|
||||
| R | httr |
|
||||
| Ruby | Native |
|
||||
| Shell | cURL, HTTPie, Wget |
|
||||
| Swift | URLSession |
|
||||
| Language | Libraries |
|
||||
| ----------- | ------------------------------------ |
|
||||
| C | libcurl |
|
||||
| Clojure | clj-http |
|
||||
| C# | HttpClient, RestSharp |
|
||||
| Go | Native |
|
||||
| HTTP | HTTP/1.1 |
|
||||
| Java | AsyncHttp, NetHttp, OkHttp, Unirest |
|
||||
| JavaScript | Axios, fetch, jQuery, XHR |
|
||||
| Kotlin | OkHttp |
|
||||
| Node.js | Axios, fetch, HTTP, Request, Unirest |
|
||||
| Objective-C | NSURLSession |
|
||||
| OCaml | CoHTTP |
|
||||
| PHP | cURL, Guzzle, HTTP v1, HTTP v2 |
|
||||
| PowerShell | Invoke-WebRequest, RestMethod |
|
||||
| Python | http.client, Requests |
|
||||
| R | httr |
|
||||
| Ruby | Native |
|
||||
| Shell | cURL, HTTPie, Wget |
|
||||
| Swift | URLSession |
|
||||
|
||||
## Features
|
||||
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
{
|
||||
"name": "@yaak/httpsnippet",
|
||||
"private": true,
|
||||
"version": "1.0.3",
|
||||
"displayName": "HTTP Snippet",
|
||||
"version": "1.0.3",
|
||||
"private": true,
|
||||
"description": "Generate code snippets for HTTP requests in various languages and frameworks",
|
||||
"minYaakVersion": "2026.2.0-beta.10",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/mountain-loop/yaak.git",
|
||||
@@ -20,5 +19,6 @@
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.0.0",
|
||||
"typescript": "^5.9.3"
|
||||
}
|
||||
},
|
||||
"minYaakVersion": "2026.2.0-beta.10"
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { availableTargets, type HarRequest, HTTPSnippet } from '@readme/httpsnippet';
|
||||
import type { EditorLanguage, HttpRequest, PluginDefinition } from '@yaakapp/api';
|
||||
import { availableTargets, type HarRequest, HTTPSnippet } from "@readme/httpsnippet";
|
||||
import type { EditorLanguage, HttpRequest, PluginDefinition } from "@yaakapp/api";
|
||||
|
||||
// Get all available targets and build select options
|
||||
const targets = availableTargets();
|
||||
|
||||
// Targets to exclude from the language list
|
||||
const excludedTargets = new Set(['json']);
|
||||
const excludedTargets = new Set(["json"]);
|
||||
|
||||
// Build language (target) options
|
||||
const languageOptions = targets
|
||||
@@ -17,8 +17,8 @@ const languageOptions = targets
|
||||
|
||||
// Preferred clients per target (shown first in the list)
|
||||
const preferredClients: Record<string, string> = {
|
||||
javascript: 'fetch',
|
||||
node: 'fetch',
|
||||
javascript: "fetch",
|
||||
node: "fetch",
|
||||
};
|
||||
|
||||
// Get client options for a given target key
|
||||
@@ -41,50 +41,50 @@ function getClientOptions(targetKey: string) {
|
||||
// Get default client for a target
|
||||
function getDefaultClient(targetKey: string): string {
|
||||
const options = getClientOptions(targetKey);
|
||||
return options[0]?.value ?? '';
|
||||
return options[0]?.value ?? "";
|
||||
}
|
||||
|
||||
// Defaults
|
||||
const defaultTarget = 'javascript';
|
||||
const defaultTarget = "javascript";
|
||||
|
||||
// Map httpsnippet target key to editor language for syntax highlighting
|
||||
const editorLanguageMap: Record<string, EditorLanguage> = {
|
||||
c: 'c',
|
||||
clojure: 'clojure',
|
||||
csharp: 'csharp',
|
||||
go: 'go',
|
||||
http: 'http',
|
||||
java: 'java',
|
||||
javascript: 'javascript',
|
||||
kotlin: 'kotlin',
|
||||
node: 'javascript',
|
||||
objc: 'objective_c',
|
||||
ocaml: 'ocaml',
|
||||
php: 'php',
|
||||
powershell: 'powershell',
|
||||
python: 'python',
|
||||
r: 'r',
|
||||
ruby: 'ruby',
|
||||
shell: 'shell',
|
||||
swift: 'swift',
|
||||
c: "c",
|
||||
clojure: "clojure",
|
||||
csharp: "csharp",
|
||||
go: "go",
|
||||
http: "http",
|
||||
java: "java",
|
||||
javascript: "javascript",
|
||||
kotlin: "kotlin",
|
||||
node: "javascript",
|
||||
objc: "objective_c",
|
||||
ocaml: "ocaml",
|
||||
php: "php",
|
||||
powershell: "powershell",
|
||||
python: "python",
|
||||
r: "r",
|
||||
ruby: "ruby",
|
||||
shell: "shell",
|
||||
swift: "swift",
|
||||
};
|
||||
|
||||
function getEditorLanguage(targetKey: string): EditorLanguage {
|
||||
return editorLanguageMap[targetKey] ?? 'text';
|
||||
return editorLanguageMap[targetKey] ?? "text";
|
||||
}
|
||||
|
||||
// Convert Yaak HttpRequest to HAR format
|
||||
function toHarRequest(request: Partial<HttpRequest>) {
|
||||
// Build URL with query parameters
|
||||
let finalUrl = request.url || '';
|
||||
let finalUrl = request.url || "";
|
||||
const urlParams = (request.urlParameters ?? []).filter((p) => p.enabled !== false && !!p.name);
|
||||
if (urlParams.length > 0) {
|
||||
const [base, hash] = finalUrl.split('#');
|
||||
const separator = base?.includes('?') ? '&' : '?';
|
||||
const [base, hash] = finalUrl.split("#");
|
||||
const separator = base?.includes("?") ? "&" : "?";
|
||||
const queryString = urlParams
|
||||
.map((p) => `${encodeURIComponent(p.name)}=${encodeURIComponent(p.value)}`)
|
||||
.join('&');
|
||||
finalUrl = base + separator + queryString + (hash ? `#${hash}` : '');
|
||||
.join("&");
|
||||
finalUrl = base + separator + queryString + (hash ? `#${hash}` : "");
|
||||
}
|
||||
|
||||
// Build headers array
|
||||
@@ -94,75 +94,75 @@ function toHarRequest(request: Partial<HttpRequest>) {
|
||||
|
||||
// Handle authentication
|
||||
if (request.authentication?.disabled !== true) {
|
||||
if (request.authenticationType === 'basic') {
|
||||
if (request.authenticationType === "basic") {
|
||||
const credentials = btoa(
|
||||
`${request.authentication?.username ?? ''}:${request.authentication?.password ?? ''}`,
|
||||
`${request.authentication?.username ?? ""}:${request.authentication?.password ?? ""}`,
|
||||
);
|
||||
headers.push({ name: 'Authorization', value: `Basic ${credentials}` });
|
||||
} else if (request.authenticationType === 'bearer') {
|
||||
const prefix = request.authentication?.prefix ?? 'Bearer';
|
||||
const token = request.authentication?.token ?? '';
|
||||
headers.push({ name: 'Authorization', value: `${prefix} ${token}`.trim() });
|
||||
} else if (request.authenticationType === 'apikey') {
|
||||
if (request.authentication?.location === 'header') {
|
||||
headers.push({ name: "Authorization", value: `Basic ${credentials}` });
|
||||
} else if (request.authenticationType === "bearer") {
|
||||
const prefix = request.authentication?.prefix ?? "Bearer";
|
||||
const token = request.authentication?.token ?? "";
|
||||
headers.push({ name: "Authorization", value: `${prefix} ${token}`.trim() });
|
||||
} else if (request.authenticationType === "apikey") {
|
||||
if (request.authentication?.location === "header") {
|
||||
headers.push({
|
||||
name: request.authentication?.key ?? 'X-Api-Key',
|
||||
value: request.authentication?.value ?? '',
|
||||
name: request.authentication?.key ?? "X-Api-Key",
|
||||
value: request.authentication?.value ?? "",
|
||||
});
|
||||
} else if (request.authentication?.location === 'query') {
|
||||
const sep = finalUrl.includes('?') ? '&' : '?';
|
||||
} else if (request.authentication?.location === "query") {
|
||||
const sep = finalUrl.includes("?") ? "&" : "?";
|
||||
finalUrl = [
|
||||
finalUrl,
|
||||
sep,
|
||||
encodeURIComponent(request.authentication?.key ?? 'token'),
|
||||
'=',
|
||||
encodeURIComponent(request.authentication?.value ?? ''),
|
||||
].join('');
|
||||
encodeURIComponent(request.authentication?.key ?? "token"),
|
||||
"=",
|
||||
encodeURIComponent(request.authentication?.value ?? ""),
|
||||
].join("");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Build HAR request object
|
||||
const har: Record<string, unknown> = {
|
||||
method: request.method || 'GET',
|
||||
method: request.method || "GET",
|
||||
url: finalUrl,
|
||||
headers,
|
||||
};
|
||||
|
||||
// Handle request body
|
||||
const bodyType = request.bodyType ?? 'none';
|
||||
if (bodyType !== 'none' && request.body) {
|
||||
if (bodyType === 'application/x-www-form-urlencoded' && Array.isArray(request.body.form)) {
|
||||
const bodyType = request.bodyType ?? "none";
|
||||
if (bodyType !== "none" && request.body) {
|
||||
if (bodyType === "application/x-www-form-urlencoded" && Array.isArray(request.body.form)) {
|
||||
const params = request.body.form
|
||||
.filter((p: { enabled?: boolean; name?: string }) => p.enabled !== false && !!p.name)
|
||||
.map((p: { name: string; value: string }) => ({ name: p.name, value: p.value }));
|
||||
har.postData = {
|
||||
mimeType: 'application/x-www-form-urlencoded',
|
||||
mimeType: "application/x-www-form-urlencoded",
|
||||
params,
|
||||
};
|
||||
} else if (bodyType === 'multipart/form-data' && Array.isArray(request.body.form)) {
|
||||
} else if (bodyType === "multipart/form-data" && Array.isArray(request.body.form)) {
|
||||
const params = request.body.form
|
||||
.filter((p: { enabled?: boolean; name?: string }) => p.enabled !== false && !!p.name)
|
||||
.map((p: { name: string; value: string; file?: string; contentType?: string }) => {
|
||||
const param: Record<string, string> = { name: p.name, value: p.value || '' };
|
||||
const param: Record<string, string> = { name: p.name, value: p.value || "" };
|
||||
if (p.file) param.fileName = p.file;
|
||||
if (p.contentType) param.contentType = p.contentType;
|
||||
return param;
|
||||
});
|
||||
har.postData = {
|
||||
mimeType: 'multipart/form-data',
|
||||
mimeType: "multipart/form-data",
|
||||
params,
|
||||
};
|
||||
} else if (bodyType === 'graphql' && typeof request.body.query === 'string') {
|
||||
} else if (bodyType === "graphql" && typeof request.body.query === "string") {
|
||||
const body = {
|
||||
query: request.body.query || '',
|
||||
query: request.body.query || "",
|
||||
variables: maybeParseJSON(request.body.variables, undefined),
|
||||
};
|
||||
har.postData = {
|
||||
mimeType: 'application/json',
|
||||
mimeType: "application/json",
|
||||
text: JSON.stringify(body),
|
||||
};
|
||||
} else if (typeof request.body.text === 'string') {
|
||||
} else if (typeof request.body.text === "string") {
|
||||
har.postData = {
|
||||
mimeType: bodyType,
|
||||
text: request.body.text,
|
||||
@@ -173,8 +173,8 @@ function toHarRequest(request: Partial<HttpRequest>) {
|
||||
return har;
|
||||
}
|
||||
|
||||
function maybeParseJSON<T>(v: unknown, fallback: T): T | unknown {
|
||||
if (typeof v !== 'string') return fallback;
|
||||
function maybeParseJSON<T>(v: unknown, fallback: T): unknown {
|
||||
if (typeof v !== "string") return fallback;
|
||||
try {
|
||||
return JSON.parse(v);
|
||||
} catch {
|
||||
@@ -185,20 +185,20 @@ function maybeParseJSON<T>(v: unknown, fallback: T): T | unknown {
|
||||
export const plugin: PluginDefinition = {
|
||||
httpRequestActions: [
|
||||
{
|
||||
label: 'Generate Code Snippet',
|
||||
icon: 'copy',
|
||||
label: "Generate Code Snippet",
|
||||
icon: "copy",
|
||||
async onSelect(ctx, args) {
|
||||
// Render the request with variables resolved
|
||||
const renderedRequest = await ctx.httpRequest.render({
|
||||
httpRequest: args.httpRequest,
|
||||
purpose: 'send',
|
||||
purpose: "send",
|
||||
});
|
||||
|
||||
// Convert to HAR format
|
||||
const harRequest = toHarRequest(renderedRequest) as HarRequest;
|
||||
|
||||
// Get previously selected language or use defaults
|
||||
const storedTarget = await ctx.store.get<string>('selectedTarget');
|
||||
const storedTarget = await ctx.store.get<string>("selectedTarget");
|
||||
const initialTarget = storedTarget || defaultTarget;
|
||||
const storedClient = await ctx.store.get<string>(`selectedClient:${initialTarget}`);
|
||||
const initialClient = storedClient || getDefaultClient(initialTarget);
|
||||
@@ -210,39 +210,39 @@ export const plugin: PluginDefinition = {
|
||||
target as Parameters<typeof snippet.convert>[0],
|
||||
client as Parameters<typeof snippet.convert>[1],
|
||||
);
|
||||
return (Array.isArray(result) ? result.join('\n') : result || '').replace(/\r\n/g, '\n');
|
||||
return (Array.isArray(result) ? result.join("\n") : result || "").replace(/\r\n/g, "\n");
|
||||
};
|
||||
|
||||
// Generate initial code preview
|
||||
let initialCode = '';
|
||||
let initialCode = "";
|
||||
try {
|
||||
initialCode = generateSnippet(initialTarget, initialClient);
|
||||
} catch {
|
||||
initialCode = '// Error generating snippet';
|
||||
initialCode = "// Error generating snippet";
|
||||
}
|
||||
|
||||
// Show dialog with language/library selectors and code preview
|
||||
const result = await ctx.prompt.form({
|
||||
id: 'httpsnippet',
|
||||
title: 'Generate Code Snippet',
|
||||
confirmText: 'Copy to Clipboard',
|
||||
cancelText: 'Cancel',
|
||||
size: 'md',
|
||||
id: "httpsnippet",
|
||||
title: "Generate Code Snippet",
|
||||
confirmText: "Copy to Clipboard",
|
||||
cancelText: "Cancel",
|
||||
size: "md",
|
||||
inputs: [
|
||||
{
|
||||
type: 'h_stack',
|
||||
type: "h_stack",
|
||||
inputs: [
|
||||
{
|
||||
type: 'select',
|
||||
name: 'target',
|
||||
label: 'Language',
|
||||
type: "select",
|
||||
name: "target",
|
||||
label: "Language",
|
||||
defaultValue: initialTarget,
|
||||
options: languageOptions,
|
||||
},
|
||||
{
|
||||
type: 'select',
|
||||
type: "select",
|
||||
name: `client-${initialTarget}`,
|
||||
label: 'Library',
|
||||
label: "Library",
|
||||
defaultValue: initialClient,
|
||||
options: getClientOptions(initialTarget),
|
||||
dynamic(_ctx, { values }) {
|
||||
@@ -251,16 +251,16 @@ export const plugin: PluginDefinition = {
|
||||
return {
|
||||
name: `client-${targetKey}`,
|
||||
options,
|
||||
defaultValue: options[0]?.value ?? '',
|
||||
defaultValue: options[0]?.value ?? "",
|
||||
};
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'editor',
|
||||
name: 'code',
|
||||
label: 'Preview',
|
||||
type: "editor",
|
||||
name: "code",
|
||||
label: "Preview",
|
||||
language: getEditorLanguage(initialTarget),
|
||||
defaultValue: initialCode,
|
||||
readOnly: true,
|
||||
@@ -274,7 +274,7 @@ export const plugin: PluginDefinition = {
|
||||
try {
|
||||
code = generateSnippet(targetKey, clientKey);
|
||||
} catch {
|
||||
code = '// Error generating snippet';
|
||||
code = "// Error generating snippet";
|
||||
}
|
||||
return {
|
||||
defaultValue: code,
|
||||
@@ -291,7 +291,7 @@ export const plugin: PluginDefinition = {
|
||||
const selectedClient = String(
|
||||
result[`client-${selectedTarget}`] || getDefaultClient(selectedTarget),
|
||||
);
|
||||
await ctx.store.set('selectedTarget', selectedTarget);
|
||||
await ctx.store.set("selectedTarget", selectedTarget);
|
||||
await ctx.store.set(`selectedClient:${selectedTarget}`, selectedClient);
|
||||
|
||||
// Generate snippet for the selected language
|
||||
@@ -299,15 +299,15 @@ export const plugin: PluginDefinition = {
|
||||
const codeText = generateSnippet(selectedTarget, selectedClient);
|
||||
await ctx.clipboard.copyText(codeText);
|
||||
await ctx.toast.show({
|
||||
message: 'Code snippet copied to clipboard',
|
||||
icon: 'copy',
|
||||
color: 'success',
|
||||
message: "Code snippet copied to clipboard",
|
||||
icon: "copy",
|
||||
color: "success",
|
||||
});
|
||||
} catch (err) {
|
||||
await ctx.toast.show({
|
||||
message: `Failed to generate snippet: ${err}`,
|
||||
icon: 'alert_triangle',
|
||||
color: 'danger',
|
||||
message: `Failed to generate snippet: ${err instanceof Error ? err.message : String(err)}`,
|
||||
icon: "alert_triangle",
|
||||
color: "danger",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
{
|
||||
"name": "@yaak/mcp-server",
|
||||
"private": true,
|
||||
"version": "0.2.1",
|
||||
"displayName": "MCP Server",
|
||||
"version": "0.2.1",
|
||||
"private": true,
|
||||
"description": "Expose Yaak functionality via Model Context Protocol",
|
||||
"minYaakVersion": "2026.1.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/mountain-loop/yaak.git",
|
||||
@@ -24,5 +23,6 @@
|
||||
"devDependencies": {
|
||||
"@types/node": "^25.0.3",
|
||||
"typescript": "^5.9.3"
|
||||
}
|
||||
},
|
||||
"minYaakVersion": "2026.1.0"
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { Context, PluginDefinition } from '@yaakapp/api';
|
||||
import { createMcpServer } from './server.js';
|
||||
import type { Context, PluginDefinition } from "@yaakapp/api";
|
||||
import { createMcpServer } from "./server.js";
|
||||
|
||||
const serverPort = parseInt(process.env.YAAK_PLUGIN_MCP_SERVER_PORT ?? '64343', 10);
|
||||
const serverPort = parseInt(process.env.YAAK_PLUGIN_MCP_SERVER_PORT ?? "64343", 10);
|
||||
|
||||
let mcpServer: Awaited<ReturnType<typeof createMcpServer>> | null = null;
|
||||
|
||||
@@ -9,16 +9,16 @@ export const plugin: PluginDefinition = {
|
||||
async init(ctx: Context) {
|
||||
// Start the server after waiting, so there's an active window open to do things
|
||||
// like show the startup toast.
|
||||
console.log('Initializing MCP Server plugin');
|
||||
console.log("Initializing MCP Server plugin");
|
||||
setTimeout(async () => {
|
||||
try {
|
||||
mcpServer = createMcpServer({ yaak: ctx }, serverPort);
|
||||
} catch (err) {
|
||||
console.error('Failed to start MCP server:', err);
|
||||
ctx.toast.show({
|
||||
console.error("Failed to start MCP server:", err);
|
||||
void ctx.toast.show({
|
||||
message: `Failed to start MCP Server: ${err instanceof Error ? err.message : String(err)}`,
|
||||
icon: 'alert_triangle',
|
||||
color: 'danger',
|
||||
icon: "alert_triangle",
|
||||
color: "danger",
|
||||
timeout: 10000,
|
||||
});
|
||||
}
|
||||
@@ -26,7 +26,7 @@ export const plugin: PluginDefinition = {
|
||||
},
|
||||
|
||||
async dispose() {
|
||||
console.log('Disposing MCP Server plugin');
|
||||
console.log("Disposing MCP Server plugin");
|
||||
|
||||
if (mcpServer) {
|
||||
await mcpServer.close();
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
import { StreamableHTTPTransport } from '@hono/mcp';
|
||||
import { serve } from '@hono/node-server';
|
||||
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
||||
import { Hono } from 'hono';
|
||||
import { registerFolderTools } from './tools/folder.js';
|
||||
import { registerHttpRequestTools } from './tools/httpRequest.js';
|
||||
import { registerToastTools } from './tools/toast.js';
|
||||
import { registerWindowTools } from './tools/window.js';
|
||||
import { registerWorkspaceTools } from './tools/workspace.js';
|
||||
import type { McpServerContext } from './types.js';
|
||||
import { StreamableHTTPTransport } from "@hono/mcp";
|
||||
import { serve } from "@hono/node-server";
|
||||
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import { Hono } from "hono";
|
||||
import { registerFolderTools } from "./tools/folder.js";
|
||||
import { registerHttpRequestTools } from "./tools/httpRequest.js";
|
||||
import { registerToastTools } from "./tools/toast.js";
|
||||
import { registerWindowTools } from "./tools/window.js";
|
||||
import { registerWorkspaceTools } from "./tools/workspace.js";
|
||||
import type { McpServerContext } from "./types.js";
|
||||
|
||||
export function createMcpServer(ctx: McpServerContext, port: number) {
|
||||
console.log('Creating MCP server on port', port);
|
||||
console.log("Creating MCP server on port", port);
|
||||
const mcpServer = new McpServer({
|
||||
name: 'yaak-mcp-server',
|
||||
version: '0.1.0',
|
||||
name: "yaak-mcp-server",
|
||||
version: "0.1.0",
|
||||
});
|
||||
|
||||
// Register all tools
|
||||
@@ -26,14 +26,14 @@ export function createMcpServer(ctx: McpServerContext, port: number) {
|
||||
const app = new Hono();
|
||||
const transport = new StreamableHTTPTransport();
|
||||
|
||||
app.all('/mcp', async (c) => {
|
||||
app.all("/mcp", async (c) => {
|
||||
if (!mcpServer.isConnected()) {
|
||||
// Connect the mcp with the transport
|
||||
await mcpServer.connect(transport);
|
||||
ctx.yaak.toast.show({
|
||||
void ctx.yaak.toast.show({
|
||||
message: `MCP Server connected`,
|
||||
icon: 'info',
|
||||
color: 'info',
|
||||
icon: "info",
|
||||
color: "info",
|
||||
timeout: 5000,
|
||||
});
|
||||
}
|
||||
@@ -43,15 +43,15 @@ export function createMcpServer(ctx: McpServerContext, port: number) {
|
||||
const honoServer = serve(
|
||||
{
|
||||
port,
|
||||
hostname: '127.0.0.1',
|
||||
hostname: "127.0.0.1",
|
||||
fetch: app.fetch,
|
||||
},
|
||||
(info) => {
|
||||
console.log('Started MCP server on ', info.address);
|
||||
ctx.yaak.toast.show({
|
||||
console.log("Started MCP server on ", info.address);
|
||||
void ctx.yaak.toast.show({
|
||||
message: `MCP Server running on http://127.0.0.1:${info.port}`,
|
||||
icon: 'info',
|
||||
color: 'secondary',
|
||||
icon: "info",
|
||||
color: "secondary",
|
||||
timeout: 10000,
|
||||
});
|
||||
},
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
||||
import * as z from 'zod';
|
||||
import type { McpServerContext } from '../types.js';
|
||||
import { getWorkspaceContext } from './helpers.js';
|
||||
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import * as z from "zod";
|
||||
import type { McpServerContext } from "../types.js";
|
||||
import { getWorkspaceContext } from "./helpers.js";
|
||||
import {
|
||||
authenticationSchema,
|
||||
authenticationTypeSchema,
|
||||
headersSchema,
|
||||
workspaceIdSchema,
|
||||
} from './schemas.js';
|
||||
} from "./schemas.js";
|
||||
|
||||
export function registerFolderTools(server: McpServer, ctx: McpServerContext) {
|
||||
server.registerTool(
|
||||
'list_folders',
|
||||
"list_folders",
|
||||
{
|
||||
title: 'List Folders',
|
||||
description: 'List all folders in a workspace',
|
||||
title: "List Folders",
|
||||
description: "List all folders in a workspace",
|
||||
inputSchema: {
|
||||
workspaceId: workspaceIdSchema,
|
||||
},
|
||||
@@ -26,7 +26,7 @@ export function registerFolderTools(server: McpServer, ctx: McpServerContext) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text' as const,
|
||||
type: "text" as const,
|
||||
text: JSON.stringify(folders, null, 2),
|
||||
},
|
||||
],
|
||||
@@ -35,12 +35,12 @@ export function registerFolderTools(server: McpServer, ctx: McpServerContext) {
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
'get_folder',
|
||||
"get_folder",
|
||||
{
|
||||
title: 'Get Folder',
|
||||
description: 'Get details of a specific folder by ID',
|
||||
title: "Get Folder",
|
||||
description: "Get details of a specific folder by ID",
|
||||
inputSchema: {
|
||||
id: z.string().describe('The folder ID'),
|
||||
id: z.string().describe("The folder ID"),
|
||||
workspaceId: workspaceIdSchema,
|
||||
},
|
||||
},
|
||||
@@ -51,7 +51,7 @@ export function registerFolderTools(server: McpServer, ctx: McpServerContext) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text' as const,
|
||||
type: "text" as const,
|
||||
text: JSON.stringify(folder, null, 2),
|
||||
},
|
||||
],
|
||||
@@ -60,17 +60,17 @@ export function registerFolderTools(server: McpServer, ctx: McpServerContext) {
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
'create_folder',
|
||||
"create_folder",
|
||||
{
|
||||
title: 'Create Folder',
|
||||
description: 'Create a new folder in a workspace',
|
||||
title: "Create Folder",
|
||||
description: "Create a new folder in a workspace",
|
||||
inputSchema: {
|
||||
workspaceId: workspaceIdSchema,
|
||||
name: z.string().describe('Folder name'),
|
||||
folderId: z.string().optional().describe('Parent folder ID (for nested folders)'),
|
||||
description: z.string().optional().describe('Folder description'),
|
||||
sortPriority: z.number().optional().describe('Sort priority for ordering'),
|
||||
headers: headersSchema.describe('Default headers to apply to requests in this folder'),
|
||||
name: z.string().describe("Folder name"),
|
||||
folderId: z.string().optional().describe("Parent folder ID (for nested folders)"),
|
||||
description: z.string().optional().describe("Folder description"),
|
||||
sortPriority: z.number().optional().describe("Sort priority for ordering"),
|
||||
headers: headersSchema.describe("Default headers to apply to requests in this folder"),
|
||||
authenticationType: authenticationTypeSchema,
|
||||
authentication: authenticationSchema,
|
||||
},
|
||||
@@ -79,7 +79,7 @@ export function registerFolderTools(server: McpServer, ctx: McpServerContext) {
|
||||
const workspaceCtx = await getWorkspaceContext(ctx, ogWorkspaceId);
|
||||
const workspaceId = await workspaceCtx.yaak.window.workspaceId();
|
||||
if (!workspaceId) {
|
||||
throw new Error('No workspace is open');
|
||||
throw new Error("No workspace is open");
|
||||
}
|
||||
|
||||
const folder = await workspaceCtx.yaak.folder.create({
|
||||
@@ -88,24 +88,24 @@ export function registerFolderTools(server: McpServer, ctx: McpServerContext) {
|
||||
});
|
||||
|
||||
return {
|
||||
content: [{ type: 'text' as const, text: JSON.stringify(folder, null, 2) }],
|
||||
content: [{ type: "text" as const, text: JSON.stringify(folder, null, 2) }],
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
'update_folder',
|
||||
"update_folder",
|
||||
{
|
||||
title: 'Update Folder',
|
||||
description: 'Update an existing folder',
|
||||
title: "Update Folder",
|
||||
description: "Update an existing folder",
|
||||
inputSchema: {
|
||||
id: z.string().describe('Folder ID to update'),
|
||||
id: z.string().describe("Folder ID to update"),
|
||||
workspaceId: workspaceIdSchema,
|
||||
name: z.string().optional().describe('Folder name'),
|
||||
folderId: z.string().optional().describe('Parent folder ID (for nested folders)'),
|
||||
description: z.string().optional().describe('Folder description'),
|
||||
sortPriority: z.number().optional().describe('Sort priority for ordering'),
|
||||
headers: headersSchema.describe('Default headers to apply to requests in this folder'),
|
||||
name: z.string().optional().describe("Folder name"),
|
||||
folderId: z.string().optional().describe("Parent folder ID (for nested folders)"),
|
||||
description: z.string().optional().describe("Folder description"),
|
||||
sortPriority: z.number().optional().describe("Sort priority for ordering"),
|
||||
headers: headersSchema.describe("Default headers to apply to requests in this folder"),
|
||||
authenticationType: authenticationTypeSchema,
|
||||
authentication: authenticationSchema,
|
||||
},
|
||||
@@ -124,24 +124,24 @@ export function registerFolderTools(server: McpServer, ctx: McpServerContext) {
|
||||
id,
|
||||
});
|
||||
return {
|
||||
content: [{ type: 'text' as const, text: JSON.stringify(folder, null, 2) }],
|
||||
content: [{ type: "text" as const, text: JSON.stringify(folder, null, 2) }],
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
'delete_folder',
|
||||
"delete_folder",
|
||||
{
|
||||
title: 'Delete Folder',
|
||||
description: 'Delete a folder by ID',
|
||||
title: "Delete Folder",
|
||||
description: "Delete a folder by ID",
|
||||
inputSchema: {
|
||||
id: z.string().describe('Folder ID to delete'),
|
||||
id: z.string().describe("Folder ID to delete"),
|
||||
},
|
||||
},
|
||||
async ({ id }) => {
|
||||
const folder = await ctx.yaak.folder.delete({ id });
|
||||
return {
|
||||
content: [{ type: 'text' as const, text: `Deleted: ${folder.name} (${folder.id})` }],
|
||||
content: [{ type: "text" as const, text: `Deleted: ${folder.name} (${folder.id})` }],
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { McpServerContext } from '../types.js';
|
||||
import type { McpServerContext } from "../types.js";
|
||||
|
||||
export async function getWorkspaceContext(
|
||||
ctx: McpServerContext,
|
||||
@@ -7,7 +7,7 @@ export async function getWorkspaceContext(
|
||||
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');
|
||||
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` +
|
||||
@@ -19,7 +19,7 @@ export async function getWorkspaceContext(
|
||||
|
||||
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');
|
||||
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}`,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
||||
import * as z from 'zod';
|
||||
import type { McpServerContext } from '../types.js';
|
||||
import { getWorkspaceContext } from './helpers.js';
|
||||
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import * as z from "zod";
|
||||
import type { McpServerContext } from "../types.js";
|
||||
import { getWorkspaceContext } from "./helpers.js";
|
||||
import {
|
||||
authenticationSchema,
|
||||
authenticationTypeSchema,
|
||||
@@ -10,14 +10,14 @@ import {
|
||||
headersSchema,
|
||||
urlParametersSchema,
|
||||
workspaceIdSchema,
|
||||
} from './schemas.js';
|
||||
} from "./schemas.js";
|
||||
|
||||
export function registerHttpRequestTools(server: McpServer, ctx: McpServerContext) {
|
||||
server.registerTool(
|
||||
'list_http_requests',
|
||||
"list_http_requests",
|
||||
{
|
||||
title: 'List HTTP Requests',
|
||||
description: 'List all HTTP requests in a workspace',
|
||||
title: "List HTTP Requests",
|
||||
description: "List all HTTP requests in a workspace",
|
||||
inputSchema: {
|
||||
workspaceId: workspaceIdSchema,
|
||||
},
|
||||
@@ -29,7 +29,7 @@ export function registerHttpRequestTools(server: McpServer, ctx: McpServerContex
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text' as const,
|
||||
type: "text" as const,
|
||||
text: JSON.stringify(requests, null, 2),
|
||||
},
|
||||
],
|
||||
@@ -38,12 +38,12 @@ export function registerHttpRequestTools(server: McpServer, ctx: McpServerContex
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
'get_http_request',
|
||||
"get_http_request",
|
||||
{
|
||||
title: 'Get HTTP Request',
|
||||
description: 'Get details of a specific HTTP request by ID',
|
||||
title: "Get HTTP Request",
|
||||
description: "Get details of a specific HTTP request by ID",
|
||||
inputSchema: {
|
||||
id: z.string().describe('The HTTP request ID'),
|
||||
id: z.string().describe("The HTTP request ID"),
|
||||
workspaceId: workspaceIdSchema,
|
||||
},
|
||||
},
|
||||
@@ -54,7 +54,7 @@ export function registerHttpRequestTools(server: McpServer, ctx: McpServerContex
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text' as const,
|
||||
type: "text" as const,
|
||||
text: JSON.stringify(request, null, 2),
|
||||
},
|
||||
],
|
||||
@@ -63,13 +63,13 @@ export function registerHttpRequestTools(server: McpServer, ctx: McpServerContex
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
'send_http_request',
|
||||
"send_http_request",
|
||||
{
|
||||
title: 'Send HTTP Request',
|
||||
description: 'Send an HTTP request and get the response',
|
||||
title: "Send HTTP Request",
|
||||
description: "Send an HTTP request and get the response",
|
||||
inputSchema: {
|
||||
id: z.string().describe('The HTTP request ID to send'),
|
||||
environmentId: z.string().optional().describe('Optional environment ID to use'),
|
||||
id: z.string().describe("The HTTP request ID to send"),
|
||||
environmentId: z.string().optional().describe("Optional environment ID to use"),
|
||||
workspaceId: workspaceIdSchema,
|
||||
},
|
||||
},
|
||||
@@ -85,7 +85,7 @@ export function registerHttpRequestTools(server: McpServer, ctx: McpServerContex
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text' as const,
|
||||
type: "text" as const,
|
||||
text: JSON.stringify(response, null, 2),
|
||||
},
|
||||
],
|
||||
@@ -94,21 +94,21 @@ export function registerHttpRequestTools(server: McpServer, ctx: McpServerContex
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
'create_http_request',
|
||||
"create_http_request",
|
||||
{
|
||||
title: 'Create HTTP Request',
|
||||
description: 'Create a new HTTP request',
|
||||
title: "Create HTTP Request",
|
||||
description: "Create a new HTTP request",
|
||||
inputSchema: {
|
||||
workspaceId: workspaceIdSchema,
|
||||
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: headersSchema.describe('Request headers'),
|
||||
.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: headersSchema.describe("Request headers"),
|
||||
urlParameters: urlParametersSchema,
|
||||
bodyType: bodyTypeSchema,
|
||||
body: bodySchema,
|
||||
@@ -120,7 +120,7 @@ export function registerHttpRequestTools(server: McpServer, ctx: McpServerContex
|
||||
const workspaceCtx = await getWorkspaceContext(ctx, ogWorkspaceId);
|
||||
const workspaceId = await workspaceCtx.yaak.window.workspaceId();
|
||||
if (!workspaceId) {
|
||||
throw new Error('No workspace is open');
|
||||
throw new Error("No workspace is open");
|
||||
}
|
||||
|
||||
const httpRequest = await workspaceCtx.yaak.httpRequest.create({
|
||||
@@ -129,25 +129,25 @@ export function registerHttpRequestTools(server: McpServer, ctx: McpServerContex
|
||||
});
|
||||
|
||||
return {
|
||||
content: [{ type: 'text' as const, text: JSON.stringify(httpRequest, null, 2) }],
|
||||
content: [{ type: "text" as const, text: JSON.stringify(httpRequest, null, 2) }],
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
'update_http_request',
|
||||
"update_http_request",
|
||||
{
|
||||
title: 'Update HTTP Request',
|
||||
description: 'Update an existing HTTP request',
|
||||
title: "Update HTTP Request",
|
||||
description: "Update an existing HTTP request",
|
||||
inputSchema: {
|
||||
id: z.string().describe('HTTP request ID to update'),
|
||||
id: z.string().describe("HTTP request ID to update"),
|
||||
workspaceId: workspaceIdSchema,
|
||||
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: headersSchema.describe('Request headers'),
|
||||
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: headersSchema.describe("Request headers"),
|
||||
urlParameters: urlParametersSchema,
|
||||
bodyType: bodyTypeSchema,
|
||||
body: bodySchema,
|
||||
@@ -169,25 +169,25 @@ export function registerHttpRequestTools(server: McpServer, ctx: McpServerContex
|
||||
id,
|
||||
});
|
||||
return {
|
||||
content: [{ type: 'text' as const, text: JSON.stringify(httpRequest, null, 2) }],
|
||||
content: [{ type: "text" as const, text: JSON.stringify(httpRequest, null, 2) }],
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
'delete_http_request',
|
||||
"delete_http_request",
|
||||
{
|
||||
title: 'Delete HTTP Request',
|
||||
description: 'Delete an HTTP request by ID',
|
||||
title: "Delete HTTP Request",
|
||||
description: "Delete an HTTP request by ID",
|
||||
inputSchema: {
|
||||
id: z.string().describe('HTTP request ID to delete'),
|
||||
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})` },
|
||||
{ type: "text" as const, text: `Deleted: ${httpRequest.name} (${httpRequest.id})` },
|
||||
],
|
||||
};
|
||||
},
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import * as z from 'zod';
|
||||
import * as z from "zod";
|
||||
|
||||
export const workspaceIdSchema = z
|
||||
.string()
|
||||
.optional()
|
||||
.describe('Workspace ID (required if multiple workspaces are open)');
|
||||
.describe("Workspace ID (required if multiple workspaces are open)");
|
||||
|
||||
export const headersSchema = z
|
||||
.array(
|
||||
@@ -24,7 +24,7 @@ export const urlParametersSchema = z
|
||||
}),
|
||||
)
|
||||
.optional()
|
||||
.describe('URL query parameters');
|
||||
.describe("URL query parameters");
|
||||
|
||||
export const bodyTypeSchema = z
|
||||
.string()
|
||||
@@ -37,7 +37,7 @@ export const bodySchema = z
|
||||
.record(z.string(), z.any())
|
||||
.optional()
|
||||
.describe(
|
||||
'Body content object. Structure varies by bodyType:\n' +
|
||||
"Body content object. Structure varies by bodyType:\n" +
|
||||
'- "binary": { filePath: "/path/to/file" }\n' +
|
||||
'- "graphql": { query: "{ users { name } }", variables: "{\\"id\\": \\"123\\"}" }\n' +
|
||||
'- "application/x-www-form-urlencoded": { form: [{ name: "key", value: "val", enabled: true }] }\n' +
|
||||
@@ -56,7 +56,7 @@ export const authenticationSchema = z
|
||||
.record(z.string(), z.any())
|
||||
.optional()
|
||||
.describe(
|
||||
'Authentication configuration object. Structure varies by authenticationType:\n' +
|
||||
"Authentication configuration object. Structure varies by authenticationType:\n" +
|
||||
'- "basic": { username: "user", password: "pass" }\n' +
|
||||
'- "bearer": { token: "abc123", prefix: "Bearer" }\n' +
|
||||
'- "oauth2": { clientId: "...", clientSecret: "...", grantType: "authorization_code", authorizationUrl: "...", accessTokenUrl: "...", scope: "...", ... }\n' +
|
||||
|
||||
@@ -1,41 +1,41 @@
|
||||
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
||||
import type { Color, Icon } from '@yaakapp/api';
|
||||
import * as z from 'zod';
|
||||
import type { McpServerContext } from '../types.js';
|
||||
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import type { Color, Icon } from "@yaakapp/api";
|
||||
import * as z from "zod";
|
||||
import type { McpServerContext } from "../types.js";
|
||||
|
||||
const ICON_VALUES = [
|
||||
'alert_triangle',
|
||||
'check',
|
||||
'check_circle',
|
||||
'chevron_down',
|
||||
'copy',
|
||||
'info',
|
||||
'pin',
|
||||
'search',
|
||||
'trash',
|
||||
"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',
|
||||
"primary",
|
||||
"secondary",
|
||||
"info",
|
||||
"success",
|
||||
"notice",
|
||||
"warning",
|
||||
"danger",
|
||||
] as const satisfies readonly Color[];
|
||||
|
||||
export function registerToastTools(server: McpServer, ctx: McpServerContext) {
|
||||
server.registerTool(
|
||||
'show_toast',
|
||||
"show_toast",
|
||||
{
|
||||
title: 'Show Toast',
|
||||
description: 'Show a toast notification in Yaak',
|
||||
title: "Show Toast",
|
||||
description: "Show a toast notification in Yaak",
|
||||
inputSchema: {
|
||||
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'),
|
||||
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 }) => {
|
||||
@@ -49,7 +49,7 @@ export function registerToastTools(server: McpServer, ctx: McpServerContext) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text' as const,
|
||||
type: "text" as const,
|
||||
text: `✓ Toast shown: "${message}"`,
|
||||
},
|
||||
],
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
||||
import type { McpServerContext } from '../types.js';
|
||||
import { getWorkspaceContext } from './helpers.js';
|
||||
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import type { McpServerContext } from "../types.js";
|
||||
import { getWorkspaceContext } from "./helpers.js";
|
||||
|
||||
export function registerWindowTools(server: McpServer, ctx: McpServerContext) {
|
||||
server.registerTool(
|
||||
'get_workspace_id',
|
||||
"get_workspace_id",
|
||||
{
|
||||
title: 'Get Workspace ID',
|
||||
description: 'Get the current workspace ID',
|
||||
title: "Get Workspace ID",
|
||||
description: "Get the current workspace ID",
|
||||
},
|
||||
async () => {
|
||||
const workspaceCtx = await getWorkspaceContext(ctx);
|
||||
@@ -16,8 +16,8 @@ export function registerWindowTools(server: McpServer, ctx: McpServerContext) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text' as const,
|
||||
text: workspaceId || 'No workspace open',
|
||||
type: "text" as const,
|
||||
text: workspaceId || "No workspace open",
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -25,10 +25,10 @@ export function registerWindowTools(server: McpServer, ctx: McpServerContext) {
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
'get_environment_id',
|
||||
"get_environment_id",
|
||||
{
|
||||
title: 'Get Environment ID',
|
||||
description: 'Get the current environment ID',
|
||||
title: "Get Environment ID",
|
||||
description: "Get the current environment ID",
|
||||
},
|
||||
async () => {
|
||||
const workspaceCtx = await getWorkspaceContext(ctx);
|
||||
@@ -37,8 +37,8 @@ export function registerWindowTools(server: McpServer, ctx: McpServerContext) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text' as const,
|
||||
text: environmentId || 'No environment selected',
|
||||
type: "text" as const,
|
||||
text: environmentId || "No environment selected",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
||||
import type { McpServerContext } from '../types.js';
|
||||
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import type { McpServerContext } from "../types.js";
|
||||
|
||||
export function registerWorkspaceTools(server: McpServer, ctx: McpServerContext) {
|
||||
server.registerTool(
|
||||
'list_workspaces',
|
||||
"list_workspaces",
|
||||
{
|
||||
title: 'List Workspaces',
|
||||
description: 'List all open workspaces in Yaak',
|
||||
title: "List Workspaces",
|
||||
description: "List all open workspaces in Yaak",
|
||||
},
|
||||
async () => {
|
||||
const workspaces = await ctx.yaak.workspace.list();
|
||||
@@ -14,7 +14,7 @@ export function registerWorkspaceTools(server: McpServer, ctx: McpServerContext)
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text' as const,
|
||||
type: "text" as const,
|
||||
text: JSON.stringify(workspaces, null, 2),
|
||||
},
|
||||
],
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { Context } from '@yaakapp/api';
|
||||
import type { Context } from "@yaakapp/api";
|
||||
|
||||
export interface McpServerContext {
|
||||
yaak: Context;
|
||||
|
||||
Reference in New Issue
Block a user