gufi-cli 0.1.50 → 0.1.52
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/docs.js +1 -5
- package/dist/index.js +1 -0
- package/dist/lib/docs-resolver.d.ts +8 -0
- package/dist/lib/docs-resolver.js +27 -0
- package/dist/mcp.d.ts +3 -1
- package/dist/mcp.js +232 -34
- package/docs/dev-guide/1-01-architecture.md +358 -0
- package/docs/dev-guide/1-02-multi-tenant.md +415 -0
- package/docs/dev-guide/1-03-column-types.md +594 -0
- package/docs/dev-guide/1-04-json-config.md +442 -0
- package/docs/dev-guide/1-05-authentication.md +427 -0
- package/docs/dev-guide/2-01-api-reference.md +564 -0
- package/docs/dev-guide/2-02-automations.md +508 -0
- package/docs/dev-guide/2-03-gufi-cli.md +568 -0
- package/docs/dev-guide/2-04-realtime.md +401 -0
- package/docs/dev-guide/2-05-permissions.md +497 -0
- package/docs/dev-guide/2-06-integrations-overview.md +104 -0
- package/docs/dev-guide/2-07-stripe.md +173 -0
- package/docs/dev-guide/2-08-nayax.md +297 -0
- package/docs/dev-guide/2-09-ourvend.md +226 -0
- package/docs/dev-guide/2-10-tns.md +177 -0
- package/docs/dev-guide/2-11-custom-http.md +268 -0
- package/docs/dev-guide/3-01-custom-views.md +555 -0
- package/docs/dev-guide/3-02-webhooks-api.md +446 -0
- package/docs/mcp/00-overview.md +329 -0
- package/docs/mcp/01-architecture.md +220 -0
- package/docs/mcp/02-modules.md +285 -0
- package/docs/mcp/03-fields.md +357 -0
- package/docs/mcp/04-views.md +613 -0
- package/docs/mcp/05-automations.md +461 -0
- package/docs/mcp/06-api.md +480 -0
- package/docs/mcp/07-packages.md +246 -0
- package/docs/mcp/08-common-errors.md +284 -0
- package/docs/mcp/09-examples.md +453 -0
- package/docs/mcp/README.md +71 -0
- package/docs/mcp/tool-descriptions.json +49 -0
- package/package.json +3 -2
package/dist/commands/docs.js
CHANGED
|
@@ -9,12 +9,8 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import * as fs from "fs";
|
|
11
11
|
import * as path from "path";
|
|
12
|
-
import { fileURLToPath } from "url";
|
|
13
12
|
import chalk from "chalk";
|
|
14
|
-
|
|
15
|
-
const __dirname = path.dirname(__filename);
|
|
16
|
-
// docs/mcp path relative to CLI
|
|
17
|
-
const DOCS_MCP_PATH = path.resolve(__dirname, "../../../../docs/mcp");
|
|
13
|
+
import { DOCS_MCP_PATH } from "../lib/docs-resolver.js";
|
|
18
14
|
// Topic to file mapping
|
|
19
15
|
const TOPIC_FILES = {
|
|
20
16
|
overview: { file: "00-overview.md", description: "Overview and workflow" },
|
package/dist/index.js
CHANGED
|
@@ -428,6 +428,7 @@ program
|
|
|
428
428
|
program
|
|
429
429
|
.command("mcp")
|
|
430
430
|
.description("Start MCP server for Claude integration (stdio transport)")
|
|
431
|
+
.option("--company <id>", "Company ID for role-based tool filtering (Workforce mode)")
|
|
431
432
|
.action(startMcpServer);
|
|
432
433
|
// ════════════════════════════════════════════════════════════════════
|
|
433
434
|
// 💜 Claude Code Integration
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolves documentation paths for both development (monorepo) and global install.
|
|
3
|
+
*
|
|
4
|
+
* - Global install: docs are bundled at <pkg>/docs/
|
|
5
|
+
* - Development: docs live at <monorepo>/docs/
|
|
6
|
+
*/
|
|
7
|
+
export declare const DOCS_MCP_PATH: string;
|
|
8
|
+
export declare const DOCS_DEV_GUIDE_PATH: string;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolves documentation paths for both development (monorepo) and global install.
|
|
3
|
+
*
|
|
4
|
+
* - Global install: docs are bundled at <pkg>/docs/
|
|
5
|
+
* - Development: docs live at <monorepo>/docs/
|
|
6
|
+
*/
|
|
7
|
+
import * as fs from "fs";
|
|
8
|
+
import * as path from "path";
|
|
9
|
+
import { fileURLToPath } from "url";
|
|
10
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
11
|
+
const __dirname = path.dirname(__filename);
|
|
12
|
+
// Package root: from dist/lib/ go up 2 levels
|
|
13
|
+
const PKG_ROOT = path.resolve(__dirname, "../..");
|
|
14
|
+
function resolveDocsPath(subdir) {
|
|
15
|
+
// 1. Package-local (global install): <pkg>/docs/<subdir>
|
|
16
|
+
const localPath = path.join(PKG_ROOT, "docs", subdir);
|
|
17
|
+
if (fs.existsSync(localPath))
|
|
18
|
+
return localPath;
|
|
19
|
+
// 2. Monorepo (development): <pkg>/../../docs/<subdir>
|
|
20
|
+
const monorepoPath = path.resolve(PKG_ROOT, "../../docs", subdir);
|
|
21
|
+
if (fs.existsSync(monorepoPath))
|
|
22
|
+
return monorepoPath;
|
|
23
|
+
// Fallback to local path (will fail gracefully downstream)
|
|
24
|
+
return localPath;
|
|
25
|
+
}
|
|
26
|
+
export const DOCS_MCP_PATH = resolveDocsPath("mcp");
|
|
27
|
+
export const DOCS_DEV_GUIDE_PATH = resolveDocsPath("dev-guide");
|
package/dist/mcp.d.ts
CHANGED
package/dist/mcp.js
CHANGED
|
@@ -48,10 +48,8 @@ const COLUMN_TYPE_NAMES = [
|
|
|
48
48
|
// For ES modules __dirname equivalent
|
|
49
49
|
const __filename = fileURLToPath(import.meta.url);
|
|
50
50
|
const __dirname = path.dirname(__filename);
|
|
51
|
-
//
|
|
52
|
-
|
|
53
|
-
// docs/dev-guide path for integration docs
|
|
54
|
-
const DOCS_DEV_GUIDE_PATH = path.resolve(__dirname, "../../../docs/dev-guide");
|
|
51
|
+
// Docs paths - resolved for both global install and monorepo dev
|
|
52
|
+
import { DOCS_MCP_PATH, DOCS_DEV_GUIDE_PATH } from "./lib/docs-resolver.js";
|
|
55
53
|
/**
|
|
56
54
|
* Parse frontmatter from markdown content
|
|
57
55
|
*/
|
|
@@ -587,6 +585,47 @@ function getDesc(toolName, fallback = "") {
|
|
|
587
585
|
}
|
|
588
586
|
// Environment parameter description (reused across tools)
|
|
589
587
|
const ENV_PARAM = { type: "string", description: "Environment: 'prod' (default) or 'dev'. Use 'dev' for development against localhost:3000" };
|
|
588
|
+
// ════════════════════════════════════════════════════════════════════════════
|
|
589
|
+
// Workforce Mode - Role-based tool filtering
|
|
590
|
+
// ════════════════════════════════════════════════════════════════════════════
|
|
591
|
+
const ADMIN_ROLES = ['admin', 'superadmin', 'consultant'];
|
|
592
|
+
function hasAdminAccess(roles) {
|
|
593
|
+
return roles.some(r => ADMIN_ROLES.includes(r.toLowerCase()));
|
|
594
|
+
}
|
|
595
|
+
// Tools that require Admin/Consultant role
|
|
596
|
+
const ADMIN_ONLY_TOOLS = [
|
|
597
|
+
'gufi_schema_modify',
|
|
598
|
+
'gufi_env',
|
|
599
|
+
'gufi_automation_scripts',
|
|
600
|
+
'gufi_automation_meta',
|
|
601
|
+
'gufi_view_pull',
|
|
602
|
+
'gufi_view_push',
|
|
603
|
+
'gufi_view_test',
|
|
604
|
+
'gufi_browse',
|
|
605
|
+
'gufi_endpoints',
|
|
606
|
+
];
|
|
607
|
+
// Workforce state (null = developer mode, everything allowed)
|
|
608
|
+
let workforceState = null;
|
|
609
|
+
function checkEntityWritePermission(table, action, state) {
|
|
610
|
+
const entityPerms = state.entityPermissions[table];
|
|
611
|
+
if (!entityPerms)
|
|
612
|
+
return false;
|
|
613
|
+
const permMap = {
|
|
614
|
+
create: 'entity:edit',
|
|
615
|
+
update: 'entity:edit',
|
|
616
|
+
delete: 'entity:delete',
|
|
617
|
+
};
|
|
618
|
+
const required = permMap[action];
|
|
619
|
+
for (const role of state.roles) {
|
|
620
|
+
const perms = entityPerms[role];
|
|
621
|
+
if (perms === '*')
|
|
622
|
+
return true;
|
|
623
|
+
if (Array.isArray(perms) && (perms.includes(required) || perms.includes(`${required}_own`))) {
|
|
624
|
+
return true;
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
return false;
|
|
628
|
+
}
|
|
590
629
|
const TOOLS = [
|
|
591
630
|
// ─────────────────────────────────────────────────────────────────────────
|
|
592
631
|
// Context & Info
|
|
@@ -944,6 +983,61 @@ Examples:
|
|
|
944
983
|
},
|
|
945
984
|
},
|
|
946
985
|
// ─────────────────────────────────────────────────────────────────────────
|
|
986
|
+
// Browse - Headless browser for any Gufi page
|
|
987
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
988
|
+
{
|
|
989
|
+
name: "gufi_browse",
|
|
990
|
+
description: `Browse any Gufi page in headless browser. Returns console logs, errors, API calls, and screenshot.
|
|
991
|
+
|
|
992
|
+
Use to visually verify UI changes, test dialogs, check table layouts, etc.
|
|
993
|
+
|
|
994
|
+
3 ways to specify the page:
|
|
995
|
+
entity: 'ventas.productos' → opens the table view (resolves module/entity automatically)
|
|
996
|
+
page: 'home' | 'automations' | 'audit' | 'views' | 'env-variables' | 'endpoints' | 'integrations' | 'agents' | 'organization' | 'database'
|
|
997
|
+
path: '/150/m308_t4136' → any raw frontend path
|
|
998
|
+
|
|
999
|
+
Examples:
|
|
1000
|
+
gufi_browse({ entity: 'ventas.productos', company_id: '150' })
|
|
1001
|
+
gufi_browse({ page: 'home', company_id: '150' })
|
|
1002
|
+
gufi_browse({ page: 'automations', company_id: '150' })
|
|
1003
|
+
gufi_browse({ entity: 'ventas.productos', company_id: '150', actions: [{ type: 'click', selector: 'text:Exportar' }, { type: 'delay', ms: 500 }] })`,
|
|
1004
|
+
inputSchema: {
|
|
1005
|
+
type: "object",
|
|
1006
|
+
properties: {
|
|
1007
|
+
path: { type: "string", description: "Raw URL path (e.g., '/150/m308_t4136'). Use entity or page shortcuts instead when possible." },
|
|
1008
|
+
entity: { type: "string", description: "Entity shortcut in 'module.entity' format (e.g., 'ventas.productos'). Resolves to table view automatically." },
|
|
1009
|
+
page: { type: "string", description: "Page shortcut: home, automations, audit, views, env-variables, endpoints, integrations, agents, organization, database" },
|
|
1010
|
+
company_id: { type: "string", description: "Company ID for authentication context" },
|
|
1011
|
+
timeout: { type: "number", description: "Navigation timeout in ms (default: 15000)" },
|
|
1012
|
+
actions: {
|
|
1013
|
+
type: "array",
|
|
1014
|
+
description: `Actions to perform after page loads. Same as gufi_view_test.
|
|
1015
|
+
- Explore: {type:'explore'}
|
|
1016
|
+
- Click: {type:'click', selector:'text:Exportar'}
|
|
1017
|
+
- Screenshot: {type:'screenshot'}
|
|
1018
|
+
- Delay: {type:'delay', ms:1000}
|
|
1019
|
+
- Fill: {type:'fill', selector:'input[name=q]', value:'test'}`,
|
|
1020
|
+
items: {
|
|
1021
|
+
type: "object",
|
|
1022
|
+
properties: {
|
|
1023
|
+
type: { type: "string", description: "explore, click, closeModals, fill, clear, select, wait, waitForText, delay, scroll, hover, screenshot, getText, eval" },
|
|
1024
|
+
selector: { type: "string", description: "CSS selector OR 'text:Button Text'" },
|
|
1025
|
+
value: { type: "string", description: "Value for fill/select" },
|
|
1026
|
+
text: { type: "string", description: "Text to wait for (waitForText)" },
|
|
1027
|
+
ms: { type: "number", description: "Milliseconds for delay" },
|
|
1028
|
+
y: { type: "number", description: "Pixels to scroll" },
|
|
1029
|
+
timeout: { type: "number", description: "Timeout for wait (default 5000)" },
|
|
1030
|
+
code: { type: "string", description: "JS code for eval action" },
|
|
1031
|
+
},
|
|
1032
|
+
},
|
|
1033
|
+
},
|
|
1034
|
+
capture_screenshot: { type: "boolean", description: "Include base64 screenshot (default: true)" },
|
|
1035
|
+
env: ENV_PARAM,
|
|
1036
|
+
},
|
|
1037
|
+
required: ["company_id"],
|
|
1038
|
+
},
|
|
1039
|
+
},
|
|
1040
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
947
1041
|
// 💜 DISABLED: Packages (not actively used, keeping MCP simple)
|
|
948
1042
|
// ─────────────────────────────────────────────────────────────────────────
|
|
949
1043
|
// gufi_package → Re-enable when marketplace packages become priority
|
|
@@ -1806,11 +1900,6 @@ const toolHandlers = {
|
|
|
1806
1900
|
// 💜 CLI: Save to ~/gufi-dev/company_<id>/view_<id>/
|
|
1807
1901
|
const viewDir = path.join(LOCAL_VIEWS_DIR, `company_${companyId}`, `view_${viewId}`);
|
|
1808
1902
|
fs.mkdirSync(viewDir, { recursive: true });
|
|
1809
|
-
// 💜 Migrate: if old path exists (~/gufi-dev/view_<id>/), remove it
|
|
1810
|
-
const oldViewDir = path.join(LOCAL_VIEWS_DIR, `view_${viewId}`);
|
|
1811
|
-
if (fs.existsSync(oldViewDir)) {
|
|
1812
|
-
fs.rmSync(oldViewDir, { recursive: true, force: true });
|
|
1813
|
-
}
|
|
1814
1903
|
// 💜 Get latest snapshot for version tracking
|
|
1815
1904
|
let latestSnapshot;
|
|
1816
1905
|
try {
|
|
@@ -1890,7 +1979,6 @@ const toolHandlers = {
|
|
|
1890
1979
|
}
|
|
1891
1980
|
// 💜 Try to get company_id from view metadata if not provided
|
|
1892
1981
|
if (!companyId && useLocal) {
|
|
1893
|
-
// Try new path first (company_*/view_*)
|
|
1894
1982
|
const companyDirs = fs.existsSync(LOCAL_VIEWS_DIR)
|
|
1895
1983
|
? fs.readdirSync(LOCAL_VIEWS_DIR).filter(d => d.startsWith("company_"))
|
|
1896
1984
|
: [];
|
|
@@ -1906,18 +1994,6 @@ const toolHandlers = {
|
|
|
1906
1994
|
break;
|
|
1907
1995
|
}
|
|
1908
1996
|
}
|
|
1909
|
-
// Fallback: old path (view_*)
|
|
1910
|
-
if (!companyId) {
|
|
1911
|
-
const oldMetaPath = path.join(LOCAL_VIEWS_DIR, `view_${viewId}`, ".gufi-view.json");
|
|
1912
|
-
if (fs.existsSync(oldMetaPath)) {
|
|
1913
|
-
try {
|
|
1914
|
-
const meta = JSON.parse(fs.readFileSync(oldMetaPath, "utf-8"));
|
|
1915
|
-
if (meta.company_id)
|
|
1916
|
-
companyId = String(meta.company_id);
|
|
1917
|
-
}
|
|
1918
|
-
catch { /* ignore */ }
|
|
1919
|
-
}
|
|
1920
|
-
}
|
|
1921
1997
|
}
|
|
1922
1998
|
if (!companyId) {
|
|
1923
1999
|
throw new Error("company_id is required. Pass it as parameter or pull the view first.");
|
|
@@ -1925,12 +2001,8 @@ const toolHandlers = {
|
|
|
1925
2001
|
let files = [];
|
|
1926
2002
|
let lastPulledSnapshot;
|
|
1927
2003
|
if (useLocal) {
|
|
1928
|
-
// 💜 CLI: Read from ~/gufi-dev/company_<id>/view_<id>/
|
|
1929
|
-
|
|
1930
|
-
if (!fs.existsSync(viewDir)) {
|
|
1931
|
-
// Fallback to old path
|
|
1932
|
-
viewDir = path.join(LOCAL_VIEWS_DIR, `view_${viewId}`);
|
|
1933
|
-
}
|
|
2004
|
+
// 💜 CLI: Read from ~/gufi-dev/company_<id>/view_<id>/
|
|
2005
|
+
const viewDir = path.join(LOCAL_VIEWS_DIR, `company_${companyId}`, `view_${viewId}`);
|
|
1934
2006
|
if (!fs.existsSync(viewDir)) {
|
|
1935
2007
|
throw new Error(`View directory not found. Run gufi_view_pull({ view_id: ${viewId}, company_id: '${companyId}' }) first.`);
|
|
1936
2008
|
}
|
|
@@ -2055,9 +2127,7 @@ const toolHandlers = {
|
|
|
2055
2127
|
}
|
|
2056
2128
|
// 💜 Update local metadata with new snapshot number (for version tracking)
|
|
2057
2129
|
if (useLocal && result.snapshot) {
|
|
2058
|
-
|
|
2059
|
-
if (!fs.existsSync(viewDir))
|
|
2060
|
-
viewDir = path.join(LOCAL_VIEWS_DIR, `view_${viewId}`);
|
|
2130
|
+
const viewDir = path.join(LOCAL_VIEWS_DIR, `company_${companyId}`, `view_${viewId}`);
|
|
2061
2131
|
const metaPath = path.join(viewDir, ".gufi-view.json");
|
|
2062
2132
|
if (fs.existsSync(metaPath)) {
|
|
2063
2133
|
try {
|
|
@@ -2189,6 +2259,58 @@ const toolHandlers = {
|
|
|
2189
2259
|
},
|
|
2190
2260
|
// gufi_view_create removed - views are created from UI, edited via pull/push
|
|
2191
2261
|
// ─────────────────────────────────────────────────────────────────────────
|
|
2262
|
+
// Browse - Headless browser for any page
|
|
2263
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
2264
|
+
async gufi_browse(params) {
|
|
2265
|
+
const { path, entity, page, company_id, timeout, actions, capture_screenshot = true, env } = params;
|
|
2266
|
+
if (!path && !entity && !page) {
|
|
2267
|
+
return { error: "One of path, entity, or page is required" };
|
|
2268
|
+
}
|
|
2269
|
+
const result = await apiRequest("/api/cli/browse", {
|
|
2270
|
+
method: "POST",
|
|
2271
|
+
body: JSON.stringify({
|
|
2272
|
+
path,
|
|
2273
|
+
entity,
|
|
2274
|
+
page,
|
|
2275
|
+
company_id,
|
|
2276
|
+
timeout: timeout || 15000,
|
|
2277
|
+
actions: actions || [],
|
|
2278
|
+
capture_screenshot,
|
|
2279
|
+
}),
|
|
2280
|
+
}, company_id, true, env);
|
|
2281
|
+
const response = {
|
|
2282
|
+
success: result.success,
|
|
2283
|
+
url: result.url,
|
|
2284
|
+
duration_ms: result.duration_ms,
|
|
2285
|
+
};
|
|
2286
|
+
if (result.console?.errors?.length > 0)
|
|
2287
|
+
response.console_errors = result.console.errors;
|
|
2288
|
+
if (result.console?.warnings?.length > 0)
|
|
2289
|
+
response.console_warnings = result.console.warnings;
|
|
2290
|
+
if (result.console?.logs?.length > 0)
|
|
2291
|
+
response.console_logs = result.console.logs;
|
|
2292
|
+
if (result.errors?.length > 0)
|
|
2293
|
+
response.errors = result.errors;
|
|
2294
|
+
const failedCalls = (result.api_calls || []).filter((c) => c.status >= 400);
|
|
2295
|
+
if (failedCalls.length > 0)
|
|
2296
|
+
response.failed_api_calls = failedCalls;
|
|
2297
|
+
response.dom = result.dom;
|
|
2298
|
+
if (result.actions?.length > 0)
|
|
2299
|
+
response.actions = result.actions;
|
|
2300
|
+
if (result.screenshot)
|
|
2301
|
+
response.screenshot = result.screenshot;
|
|
2302
|
+
if (!result.success) {
|
|
2303
|
+
response._hint = `Browse failed: ${result.error}. Check console_errors and errors for details.`;
|
|
2304
|
+
}
|
|
2305
|
+
else if (result.errors?.length > 0 || result.console?.errors?.length > 0) {
|
|
2306
|
+
response._hint = "Page loaded but has errors. Check console_errors and errors arrays.";
|
|
2307
|
+
}
|
|
2308
|
+
else {
|
|
2309
|
+
response._hint = "Page loaded successfully. Check screenshot to verify visual appearance.";
|
|
2310
|
+
}
|
|
2311
|
+
return response;
|
|
2312
|
+
},
|
|
2313
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
2192
2314
|
// Packages
|
|
2193
2315
|
// ─────────────────────────────────────────────────────────────────────────
|
|
2194
2316
|
async gufi_package(params) {
|
|
@@ -2690,16 +2812,71 @@ async function handleRequest(request) {
|
|
|
2690
2812
|
case "notifications/initialized":
|
|
2691
2813
|
// No response needed for notifications
|
|
2692
2814
|
break;
|
|
2693
|
-
case "tools/list":
|
|
2815
|
+
case "tools/list": {
|
|
2816
|
+
let visibleTools = TOOLS;
|
|
2817
|
+
if (workforceState && !workforceState.isAdmin) {
|
|
2818
|
+
visibleTools = TOOLS.filter(t => !ADMIN_ONLY_TOOLS.includes(t.name));
|
|
2819
|
+
// Check if user has write on ANY entity - if not, adjust gufi_data description
|
|
2820
|
+
const hasAnyWrite = Object.values(workforceState.entityPermissions).some((perms) => {
|
|
2821
|
+
for (const role of workforceState.roles) {
|
|
2822
|
+
const p = perms[role];
|
|
2823
|
+
if (p === '*' || (Array.isArray(p) && p.some((x) => x.includes('edit'))))
|
|
2824
|
+
return true;
|
|
2825
|
+
}
|
|
2826
|
+
return false;
|
|
2827
|
+
});
|
|
2828
|
+
if (!hasAnyWrite) {
|
|
2829
|
+
visibleTools = visibleTools.map(t => {
|
|
2830
|
+
if (t.name !== 'gufi_data')
|
|
2831
|
+
return t;
|
|
2832
|
+
return {
|
|
2833
|
+
...t,
|
|
2834
|
+
description: t.description.replace(/create:.*?delete:.*?\n/gs, '') + '\n\nNote: Your role only has read access (list, get, aggregate).',
|
|
2835
|
+
};
|
|
2836
|
+
});
|
|
2837
|
+
}
|
|
2838
|
+
}
|
|
2694
2839
|
sendResponse({
|
|
2695
2840
|
jsonrpc: "2.0",
|
|
2696
2841
|
id,
|
|
2697
|
-
result: { tools:
|
|
2842
|
+
result: { tools: visibleTools },
|
|
2698
2843
|
});
|
|
2699
2844
|
break;
|
|
2845
|
+
}
|
|
2700
2846
|
case "tools/call": {
|
|
2701
2847
|
const toolName = params?.name;
|
|
2702
2848
|
const toolParams = params?.arguments || {};
|
|
2849
|
+
// Workforce permission check
|
|
2850
|
+
if (workforceState && !workforceState.isAdmin) {
|
|
2851
|
+
if (ADMIN_ONLY_TOOLS.includes(toolName)) {
|
|
2852
|
+
sendResponse({
|
|
2853
|
+
jsonrpc: "2.0",
|
|
2854
|
+
id,
|
|
2855
|
+
result: {
|
|
2856
|
+
content: [{ type: "text", text: JSON.stringify({
|
|
2857
|
+
error: `Permission denied: tool '${toolName}' requires Admin/Consultant role. Your roles: [${workforceState.roles.join(', ')}]`
|
|
2858
|
+
}, null, 2) }],
|
|
2859
|
+
isError: true,
|
|
2860
|
+
},
|
|
2861
|
+
});
|
|
2862
|
+
return;
|
|
2863
|
+
}
|
|
2864
|
+
if (toolName === 'gufi_data' && ['create', 'update', 'delete'].includes(toolParams.action)) {
|
|
2865
|
+
if (!checkEntityWritePermission(toolParams.table, toolParams.action, workforceState)) {
|
|
2866
|
+
sendResponse({
|
|
2867
|
+
jsonrpc: "2.0",
|
|
2868
|
+
id,
|
|
2869
|
+
result: {
|
|
2870
|
+
content: [{ type: "text", text: JSON.stringify({
|
|
2871
|
+
error: `Permission denied: your role cannot '${toolParams.action}' on table '${toolParams.table}'`
|
|
2872
|
+
}, null, 2) }],
|
|
2873
|
+
isError: true,
|
|
2874
|
+
},
|
|
2875
|
+
});
|
|
2876
|
+
return;
|
|
2877
|
+
}
|
|
2878
|
+
}
|
|
2879
|
+
}
|
|
2703
2880
|
const handler = toolHandlers[toolName];
|
|
2704
2881
|
if (!handler) {
|
|
2705
2882
|
sendError(id, -32601, `Unknown tool: ${toolName}`);
|
|
@@ -2771,7 +2948,7 @@ async function handleRequest(request) {
|
|
|
2771
2948
|
sendError(id, -32603, err.message);
|
|
2772
2949
|
}
|
|
2773
2950
|
}
|
|
2774
|
-
export async function startMcpServer() {
|
|
2951
|
+
export async function startMcpServer(options) {
|
|
2775
2952
|
// Check if logged in
|
|
2776
2953
|
if (!isLoggedIn()) {
|
|
2777
2954
|
const token = await autoLogin();
|
|
@@ -2780,6 +2957,27 @@ export async function startMcpServer() {
|
|
|
2780
2957
|
process.exit(1);
|
|
2781
2958
|
}
|
|
2782
2959
|
}
|
|
2960
|
+
// Workforce mode: fetch user permissions for the specified company
|
|
2961
|
+
if (options?.company) {
|
|
2962
|
+
try {
|
|
2963
|
+
const env = getDefaultEnv();
|
|
2964
|
+
const perms = await apiRequestWithEnv("/api/cli/my-permissions", {}, options.company, env);
|
|
2965
|
+
workforceState = {
|
|
2966
|
+
companyId: options.company,
|
|
2967
|
+
roles: perms.roles || [],
|
|
2968
|
+
platformRole: perms.platform_role || 'client',
|
|
2969
|
+
isAdmin: hasAdminAccess(perms.roles || []),
|
|
2970
|
+
entityPermissions: Object.fromEntries((perms.entity_permissions || []).map((e) => [
|
|
2971
|
+
`${e.module_name}.${e.name}`, e.permissions
|
|
2972
|
+
])),
|
|
2973
|
+
};
|
|
2974
|
+
console.error(`[gufi-mcp] Workforce mode: company=${options.company}, roles=[${workforceState.roles}], admin=${workforceState.isAdmin}`);
|
|
2975
|
+
}
|
|
2976
|
+
catch (err) {
|
|
2977
|
+
console.error(`[gufi-mcp] Failed to fetch permissions: ${err.message}`);
|
|
2978
|
+
process.exit(1);
|
|
2979
|
+
}
|
|
2980
|
+
}
|
|
2783
2981
|
const rl = readline.createInterface({
|
|
2784
2982
|
input: process.stdin,
|
|
2785
2983
|
output: process.stdout,
|