@robosystems/core 0.5.9 → 0.6.1

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.
@@ -406,58 +406,50 @@ export function ConsoleContent({ config }) {
406
406
  }
407
407
  };
408
408
  const showMcpSetup = async () => {
409
- addSystemMessage('Creating MCP API key...', true);
409
+ addSystemMessage('Creating graph-scoped connector key...', true);
410
410
  const apiUrl = process.env.NEXT_PUBLIC_ROBOSYSTEMS_API_URL ||
411
411
  'https://api.robosystems.ai';
412
+ const { mcp } = config;
413
+ const contextId = graphId || mcp.contextIdFallback;
412
414
  try {
413
- const { createUserApiKey } = await import('@robosystems/client/sdk');
414
- const response = await createUserApiKey({
415
- body: {
416
- name: `MCP - Console Generated - ${new Date().toLocaleDateString()}`,
417
- },
418
- });
419
- if (!response.data) {
420
- throw new Error('Failed to create API key');
421
- }
422
- const apiKey = response.data.key;
423
- const { mcp } = config;
424
- const contextId = graphId || mcp.contextIdFallback;
415
+ const { createMcpConnectorUrl } = await import('../../lib/mcp-connector');
425
416
  // The graph id lives in the URL path and never becomes a tool argument,
426
417
  // so one connector is anchored to exactly one graph. Multi-graph users
427
418
  // add one connector per graph, which is why the name carries the id.
428
- const mcpUrl = `${apiUrl}/v1/graphs/${contextId}/mcp`;
419
+ // The key is graph-scoped: valid only for this graph, revocable in
420
+ // Settings → API Keys, and rejected on every account-level surface.
421
+ const connector = await createMcpConnectorUrl(contextId, { apiUrl });
429
422
  const connectorName = `${mcp.serverName}-${contextId}`;
430
423
  const exampleLines = mcp.exampleQuestions
431
424
  .map((q) => ` • "${q}"`)
432
425
  .join('\n');
433
426
  addSystemMessage(`MCP Setup Instructions:\n` +
434
427
  `═══════════════════════════════════════════════════════════════\n\n` +
435
- `API Key Created Successfully!\n\n` +
436
- `One URL, one header — no install required.\n\n` +
437
- ` URL: ${mcpUrl}\n` +
438
- ` Header: X-API-Key: ${apiKey}\n\n` +
428
+ `Graph-scoped API key created — it works only for ${contextId}\n` +
429
+ `and can be revoked anytime in Settings → API Keys.\n\n` +
439
430
  `Claude (claude.ai / Desktop)\n` +
440
- ` Settings → Connectors → Add custom connector, then paste the URL\n` +
441
- ` above and add the header.\n\n` +
431
+ ` Settings → Connectors → Add custom connector, then paste this\n` +
432
+ ` URL (the key rides inside it — no header field needed):\n` +
433
+ ` ${connector.url}\n\n` +
442
434
  `Claude Code\n` +
443
435
  ` claude mcp add --transport http ${connectorName} \\\n` +
444
- ` ${mcpUrl} \\\n` +
445
- ` --header "X-API-Key: ${apiKey}"\n\n` +
436
+ ` ${connector.endpoint} \\\n` +
437
+ ` --header "X-API-Key: ${connector.apiKey}"\n\n` +
446
438
  `Cursor / VS Code (mcp.json)\n` +
447
439
  ` "${connectorName}": {\n` +
448
- ` "url": "${mcpUrl}",\n` +
449
- ` "headers": { "X-API-Key": "${apiKey}" }\n` +
440
+ ` "url": "${connector.endpoint}",\n` +
441
+ ` "headers": { "X-API-Key": "${connector.apiKey}" }\n` +
450
442
  ` }\n\n` +
451
443
  `Once connected, ask Claude questions like:\n` +
452
444
  `${exampleLines}\n\n` +
453
- `Clients without HTTP transport support can use the legacy stdio\n` +
454
- `bridge: https://github.com/RoboFinSystems/robosystems-mcp-client\n\n` +
455
- `Keep this API key secure! It has full access to your account.`, true);
445
+ `Clients without HTTP transport support can use the stdio bridge\n` +
446
+ `in proxy mode: https://github.com/RoboFinSystems/robosystems-mcp-client\n\n` +
447
+ `The connector URL contains the key — treat it like a password.`, true);
456
448
  }
457
449
  catch (error) {
458
- addErrorMessage(`Failed to create API key: ${error.message || 'Unknown error'}\n\n` +
450
+ addErrorMessage(`Failed to create connector key: ${error.message || 'Unknown error'}\n\n` +
459
451
  `You can create an API key in Settings and connect with:\n` +
460
- ` URL: ${apiUrl}/v1/graphs/${graphId || config.mcp.contextIdFallback}/mcp\n` +
452
+ ` URL: ${apiUrl}/v1/graphs/${contextId}/mcp\n` +
461
453
  ` Header: X-API-Key: <your key>`);
462
454
  }
463
455
  };
package/index.d.ts CHANGED
@@ -4,7 +4,7 @@ export { clearUserCookies, clearUserLocalStorage, clearUserSessionStorage, listU
4
4
  export { AppSwitcher, AuthGuard, AuthProvider, SignInForm, SignUpForm, useAuth, } from './auth-components';
5
5
  export { useApiError, useMediaQuery, useToast, useUser, useUserLimits, type ToastMessage, } from './hooks';
6
6
  export { createGraphContext, createGraphProvider, createUseGraphContext, EntityProvider, GraphContext, GraphProvider, OrgProvider, ServiceOfferingsProvider, SidebarProvider, useEntity, useGraphContext, useOrg, useServiceOfferings, useSidebarContext, type EntityContextValue, type EntityProviderProps, type GraphContextValue, type GraphProviderProps, type GraphState, type OrgContextState, type OrgContextValue, type OrgProviderProps, type ServiceOfferings, } from './contexts';
7
- export { clientGraphCookie, clientSidebarCookie, entityCookie, graphCookie, sidebarCookie, type EntityCookie, type GraphCookie, type SidebarCookie, } from './lib';
7
+ export { clientGraphCookie, clientSidebarCookie, createMcpConnectorUrl, entityCookie, graphCookie, sidebarCookie, type EntityCookie, type GraphCookie, type McpConnectorUrl, type SidebarCookie, } from './lib';
8
8
  export { generateEntityUri, isUUID, UUID_REGEX } from './utils';
9
9
  export { clearGraphSelection, getGraphSelection, persistGraphSelection, } from './actions/graph-actions';
10
10
  export { clearEntitySelection, getEntitySelection, persistEntitySelection, } from './actions/entity-actions';
package/index.js CHANGED
@@ -20,7 +20,7 @@ OrgProvider,
20
20
  // Service offerings context
21
21
  ServiceOfferingsProvider, SidebarProvider, useEntity, useGraphContext, useOrg, useServiceOfferings, useSidebarContext, } from './contexts';
22
22
  // Export lib utilities
23
- export { clientGraphCookie, clientSidebarCookie, entityCookie, graphCookie, sidebarCookie, } from './lib';
23
+ export { clientGraphCookie, clientSidebarCookie, createMcpConnectorUrl, entityCookie, graphCookie, sidebarCookie, } from './lib';
24
24
  // Export utils
25
25
  export { generateEntityUri, isUUID, UUID_REGEX } from './utils';
26
26
  // Export server actions
package/lib/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
1
  export { clientSidebarCookie, sidebarCookie, type SidebarCookie, } from './sidebar-cookie';
2
2
  export { clientGraphCookie, graphCookie, type GraphCookie, } from './graph-cookie';
3
3
  export { entityCookie, type EntityCookie } from './entity-cookie';
4
+ export { createMcpConnectorUrl, type McpConnectorUrl } from './mcp-connector';
package/lib/index.js CHANGED
@@ -1,3 +1,4 @@
1
1
  export { clientSidebarCookie, sidebarCookie, } from './sidebar-cookie';
2
2
  export { clientGraphCookie, graphCookie, } from './graph-cookie';
3
3
  export { entityCookie } from './entity-cookie';
4
+ export { createMcpConnectorUrl } from './mcp-connector';
@@ -0,0 +1,29 @@
1
+ export interface McpConnectorUrl {
2
+ /** Pasteable connector URL with the graph-scoped key embedded as `?token=` */
3
+ url: string;
4
+ /** Bare MCP endpoint URL (no credential) — for header-capable clients */
5
+ endpoint: string;
6
+ /** The plaintext graph-scoped API key (returned once at mint) */
7
+ apiKey: string;
8
+ /** Name the key was created under (visible in Settings → API Keys) */
9
+ keyName: string;
10
+ graphId: string;
11
+ }
12
+ /**
13
+ * Mint a graph-scoped API key and assemble the MCP connector URL for it.
14
+ *
15
+ * The URL is for clients that cannot send custom headers (claude.ai /
16
+ * Claude Desktop custom connectors): the scoped key travels as a `token`
17
+ * query parameter, which the server honors only on the MCP endpoint and
18
+ * only for keys scoped to that URL's graph — account-wide keys are
19
+ * rejected there. The key works for exactly this graph (and its
20
+ * subgraphs), is rejected on every account-level surface, and can be
21
+ * revoked from Settings → API Keys like any other key.
22
+ *
23
+ * Header-capable clients (Claude Code, Cursor, VS Code) should use the
24
+ * bare `endpoint` with the key in an `X-API-Key` header instead.
25
+ */
26
+ export declare function createMcpConnectorUrl(graphId: string, options?: {
27
+ apiUrl?: string;
28
+ name?: string;
29
+ }): Promise<McpConnectorUrl>;
@@ -0,0 +1,35 @@
1
+ const DEFAULT_API_URL = 'https://api.robosystems.ai';
2
+ /**
3
+ * Mint a graph-scoped API key and assemble the MCP connector URL for it.
4
+ *
5
+ * The URL is for clients that cannot send custom headers (claude.ai /
6
+ * Claude Desktop custom connectors): the scoped key travels as a `token`
7
+ * query parameter, which the server honors only on the MCP endpoint and
8
+ * only for keys scoped to that URL's graph — account-wide keys are
9
+ * rejected there. The key works for exactly this graph (and its
10
+ * subgraphs), is rejected on every account-level surface, and can be
11
+ * revoked from Settings → API Keys like any other key.
12
+ *
13
+ * Header-capable clients (Claude Code, Cursor, VS Code) should use the
14
+ * bare `endpoint` with the key in an `X-API-Key` header instead.
15
+ */
16
+ export async function createMcpConnectorUrl(graphId, options = {}) {
17
+ var _a, _b;
18
+ const { createUserApiKey } = await import('@robosystems/client/sdk');
19
+ const apiUrl = ((_a = options.apiUrl) !== null && _a !== void 0 ? _a : DEFAULT_API_URL).replace(/\/$/, '');
20
+ const keyName = (_b = options.name) !== null && _b !== void 0 ? _b : `MCP Connector - ${graphId} - ${new Date().toLocaleDateString()}`;
21
+ const response = await createUserApiKey({
22
+ body: { name: keyName, graph_id: graphId },
23
+ });
24
+ if (!response.data) {
25
+ throw new Error('Failed to create connector API key');
26
+ }
27
+ const endpoint = `${apiUrl}/v1/graphs/${graphId}/mcp`;
28
+ return {
29
+ url: `${endpoint}?token=${response.data.key}`,
30
+ endpoint,
31
+ apiKey: response.data.key,
32
+ keyName,
33
+ graphId,
34
+ };
35
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@robosystems/core",
3
- "version": "0.5.9",
3
+ "version": "0.6.1",
4
4
  "description": "Shared RoboSystems frontend core library (auth, contexts, task monitoring, UI components) for the RoboSystems Next.js apps",
5
5
  "license": "MIT",
6
6
  "private": false,
@@ -27,7 +27,7 @@
27
27
  "tailwind-merge": "^3"
28
28
  },
29
29
  "peerDependencies": {
30
- "@robosystems/client": ">=0.6.2 <2",
30
+ "@robosystems/client": ">=1.2 <2",
31
31
  "flowbite-react": "^0.12.5",
32
32
  "next": ">=15 <17",
33
33
  "react": ">=18 <20",
@@ -13,5 +13,5 @@ const defaultFormatDate = (date) => {
13
13
  export const ApiKeyTable = ({ apiKeys = [], onRevokeKey, theme, formatDate = defaultFormatDate, }) => {
14
14
  // Defensive check to ensure apiKeys is always an array
15
15
  const safeApiKeys = Array.isArray(apiKeys) ? apiKeys : [];
16
- return (_jsx("div", { className: "overflow-x-auto", children: _jsxs(Table, { className: "table-fixed", children: [_jsxs(TableHead, { children: [_jsx(TableHeadCell, { className: "w-[40%]", children: "Name" }), _jsx(TableHeadCell, { className: "w-[15%] whitespace-nowrap", children: "Created" }), _jsx(TableHeadCell, { className: "w-[15%] whitespace-nowrap", children: "Last Used" }), _jsx(TableHeadCell, { className: "w-[15%] whitespace-nowrap", children: "Status" }), _jsx(TableHeadCell, { className: "w-[15%] text-right", children: "Actions" })] }), _jsx(TableBody, { className: "divide-y", children: safeApiKeys.map((key) => (_jsxs(TableRow, { className: "dark:border-zinc-700", children: [_jsx(TableCell, { className: "font-medium text-zinc-900 dark:text-zinc-100", children: _jsxs("div", { className: "flex min-w-0 items-center gap-2", children: [_jsx(HiKey, { className: "h-5 w-5 shrink-0 text-zinc-600 dark:text-zinc-400" }), _jsx("span", { className: "truncate", title: key.name, children: key.name }), key.isSystem && (_jsx("span", { className: "bg-primary-100 text-primary-800 dark:bg-primary-900 dark:text-primary-300 shrink-0 rounded-full px-2.5 py-0.5 text-xs font-medium", children: "System" }))] }) }), _jsx(TableCell, { className: "whitespace-nowrap text-zinc-600 dark:text-zinc-400", children: formatDate(key.createdAt) }), _jsx(TableCell, { className: "whitespace-nowrap text-zinc-600 dark:text-zinc-400", children: formatDate(key.lastUsedAt) }), _jsx(TableCell, { className: "whitespace-nowrap", children: key.isActive ? (_jsxs("div", { className: "inline-flex items-center rounded-full bg-green-100 px-2 py-0.5 text-xs font-medium text-green-800 dark:bg-green-800 dark:text-green-100", children: [_jsx(HiCheckCircle, { className: "mr-1 h-3 w-3" }), "Active"] })) : (_jsxs("div", { className: "inline-flex items-center rounded-full bg-red-100 px-2 py-0.5 text-xs font-medium text-red-800 dark:bg-red-800 dark:text-red-100", children: [_jsx(HiExclamationCircle, { className: "mr-1 h-3 w-3" }), "Revoked"] })) }), _jsx(TableCell, { className: "text-right", children: _jsx("div", { className: "inline-flex justify-end", children: _jsx(Dropdown, { theme: theme === null || theme === void 0 ? void 0 : theme.dropdown, label: "", dismissOnClick: true, renderTrigger: () => (_jsx(Button, { theme: theme === null || theme === void 0 ? void 0 : theme.button, size: "sm", pill: true, className: "bg-zinc-100 text-zinc-900 hover:bg-zinc-200 dark:bg-zinc-700 dark:text-zinc-100 dark:hover:bg-zinc-600", children: _jsx(HiDotsVertical, { className: "h-4 w-4" }) })), children: _jsx(DropdownItem, { onClick: () => onRevokeKey(key.id), disabled: !key.isActive || key.isSystem, icon: HiTrash, className: "hover:bg-zinc-100 dark:hover:bg-zinc-700", children: "Revoke" }) }) }) })] }, key.id))) })] }) }));
16
+ return (_jsx("div", { className: "overflow-x-auto", children: _jsxs(Table, { className: "table-fixed", children: [_jsxs(TableHead, { children: [_jsx(TableHeadCell, { className: "w-[40%]", children: "Name" }), _jsx(TableHeadCell, { className: "w-[15%] whitespace-nowrap", children: "Created" }), _jsx(TableHeadCell, { className: "w-[15%] whitespace-nowrap", children: "Last Used" }), _jsx(TableHeadCell, { className: "w-[15%] whitespace-nowrap", children: "Status" }), _jsx(TableHeadCell, { className: "w-[15%] text-right", children: "Actions" })] }), _jsx(TableBody, { className: "divide-y", children: safeApiKeys.map((key) => (_jsxs(TableRow, { className: "dark:border-zinc-700", children: [_jsx(TableCell, { className: "font-medium text-zinc-900 dark:text-zinc-100", children: _jsxs("div", { className: "flex min-w-0 items-center gap-2", children: [_jsx(HiKey, { className: "h-5 w-5 shrink-0 text-zinc-600 dark:text-zinc-400" }), _jsx("span", { className: "truncate", title: key.name, children: key.name }), key.isSystem && (_jsx("span", { className: "bg-primary-100 text-primary-800 dark:bg-primary-900 dark:text-primary-300 shrink-0 rounded-full px-2.5 py-0.5 text-xs font-medium", children: "System" })), key.graphId && (_jsx("span", { className: "max-w-40 shrink-0 truncate rounded-full bg-blue-100 px-2.5 py-0.5 text-xs font-medium text-blue-800 dark:bg-blue-900 dark:text-blue-300", title: `Scoped to graph ${key.graphId} — valid only for that graph and its subgraphs`, children: key.graphId }))] }) }), _jsx(TableCell, { className: "whitespace-nowrap text-zinc-600 dark:text-zinc-400", children: formatDate(key.createdAt) }), _jsx(TableCell, { className: "whitespace-nowrap text-zinc-600 dark:text-zinc-400", children: formatDate(key.lastUsedAt) }), _jsx(TableCell, { className: "whitespace-nowrap", children: key.isActive ? (_jsxs("div", { className: "inline-flex items-center rounded-full bg-green-100 px-2 py-0.5 text-xs font-medium text-green-800 dark:bg-green-800 dark:text-green-100", children: [_jsx(HiCheckCircle, { className: "mr-1 h-3 w-3" }), "Active"] })) : (_jsxs("div", { className: "inline-flex items-center rounded-full bg-red-100 px-2 py-0.5 text-xs font-medium text-red-800 dark:bg-red-800 dark:text-red-100", children: [_jsx(HiExclamationCircle, { className: "mr-1 h-3 w-3" }), "Revoked"] })) }), _jsx(TableCell, { className: "text-right", children: _jsx("div", { className: "inline-flex justify-end", children: _jsx(Dropdown, { theme: theme === null || theme === void 0 ? void 0 : theme.dropdown, label: "", dismissOnClick: true, renderTrigger: () => (_jsx(Button, { theme: theme === null || theme === void 0 ? void 0 : theme.button, size: "sm", pill: true, className: "bg-zinc-100 text-zinc-900 hover:bg-zinc-200 dark:bg-zinc-700 dark:text-zinc-100 dark:hover:bg-zinc-600", children: _jsx(HiDotsVertical, { className: "h-4 w-4" }) })), children: _jsx(DropdownItem, { onClick: () => onRevokeKey(key.id), disabled: !key.isActive || key.isSystem, icon: HiTrash, className: "hover:bg-zinc-100 dark:hover:bg-zinc-700", children: "Revoke" }) }) }) })] }, key.id))) })] }) }));
17
17
  };