Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ services:
- NEXTAUTH_URL=http://localhost:3002
- NEXTAUTH_SECRET=gVRJMFKtjZmfNuLt3ZoxWTMujSwkig
- IAM_GUEST_CONVERSION_REFERENCE_SECRET=odZhhAyEfv9iVi7IHKh4XsEo
- IAM_MCP_ACCESS_ENCRYPTION_SECRET=oruOYeLk8Ykqbjrf3J1qvVgD
- SHARING_ENCRYPTION_SECRET=m2o0JBqYcDZxk7FUDrg0NBeq
- PROCEED_PUBLIC_IAM_ACTIVE=true
- PROCEED_PUBLIC_IAM_LOGIN_USER_PASSWORD_ACTIVE=true
Expand All @@ -19,6 +18,7 @@ services:
- PROCEED_PUBLIC_PROCESS_AUTOMATION_ACTIVE=true
- PROCEED_PUBLIC_PROCESS_AUTOMATION_TASK_EDITOR_ACTIVE=true
- PROCEED_PUBLIC_COMPETENCE_MATCHING_ACTIVE=true
- PROCEED_PUBLIC_MCP_ACTIVE=true
ports:
- '3002:33081'
depends_on:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -181,8 +181,8 @@ const basePermissionOptions: PermissionCategory[] = [
{
key: 'Manage Executions',
title: 'Manage Executions',
description: 'Allows a user to to start, modify and delete process executions.',
permission: 'view',
description: 'Allows a user to start, modify and delete process executions.',
permission: 'manage',
},
],
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import Bar from '@/components/bar';
import { OrganizationEnvironment } from '@/lib/data/environment-schema';
import { App, Button, Space } from 'antd';
import { FC } from 'react';
import { FC, use } from 'react';
import useFuzySearch, { ReplaceKeysWithHighlighted } from '@/lib/useFuzySearch';
import ElementList from '@/components/item-list-view';
import Link from 'next/link';
Expand All @@ -14,6 +14,7 @@ import { useRouter } from 'next/navigation';
import { SettingOutlined } from '@ant-design/icons';
import { getPairingCode } from '@/lib/data/mcp-authorization';
import { isUserErrorResponse } from '@/lib/user-error';
import { EnvVarsContext } from '@/components/env-vars-context';

const highlightedKeys = ['name', 'description'] as const;
export type FilteredEnvironment = ReplaceKeysWithHighlighted<
Expand All @@ -38,6 +39,8 @@ const EnvironmentsPage: FC<{
transformData: (results) => results.map((result) => result.item),
});

const env = use(EnvVarsContext);

const handleCreateAccessCode = async (environmentId: string) => {
const code = await getPairingCode(environmentId);

Expand Down Expand Up @@ -76,9 +79,11 @@ const EnvironmentsPage: FC<{
<Link href={`/${id}/start`}>
<Button>Enter</Button>
</Link>
<Button onClick={() => handleCreateAccessCode(environment.id)}>
Connect Chatbot
</Button>
{env.PROCEED_PUBLIC_MCP_ACTIVE && (
<Button onClick={() => handleCreateAccessCode(environment.id)}>
Connect Chatbot
</Button>
)}
{environment.isOrganization && (
<ConfirmationButton
title={`Leave ${environment.name.value}`}
Expand Down
16 changes: 16 additions & 0 deletions src/management-system-v2/lib/data/mcp-authorization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { isUserErrorResponse, userError } from '../user-error';
import { getTokenHash } from '../email-verification-tokens/utils';

import crypto from 'crypto';
import { getMSConfig } from '../ms-config/ms-config';

const getUserData = async () => {
const { user } = await getCurrentUser();
Expand All @@ -21,6 +22,11 @@ const getUserData = async () => {

export const getPairingCode = async (environmentId: string) => {
try {
const msConfig = await getMSConfig();
if (!msConfig.PROCEED_PUBLIC_MCP_ACTIVE) {
return userError('Not available.');
}

const user = await getUserData();
if (isUserErrorResponse(user)) return user;

Expand Down Expand Up @@ -51,6 +57,11 @@ export const getPairingCode = async (environmentId: string) => {

export const getPairingInfo = async (code: string) => {
try {
const msConfig = await getMSConfig();
if (!msConfig.PROCEED_PUBLIC_MCP_ACTIVE) {
return userError('Not available.');
}

const codeHash = await getTokenHash(code);

const pairingInfo = await _getPairingCodeInfo(codeHash);
Expand All @@ -69,6 +80,11 @@ export const getPairingInfo = async (code: string) => {
};

export const revokePairingCodes = async () => {
const msConfig = await getMSConfig();
if (!msConfig.PROCEED_PUBLIC_MCP_ACTIVE) {
return userError('Not available.');
}

const user = await getUserData();
if (isUserErrorResponse(user)) return user;

Expand Down
10 changes: 7 additions & 3 deletions src/management-system-v2/lib/data/space-settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,15 @@ import {
populateSpaceSettingsGroup as _populateSpaceSettingsGroup,
updateSpaceSettings as _updateSpaceSettings,
} from '@/lib/data/db/space-settings';
import { UnauthorizedError } from '../ability/abilityHelper';
import Ability, { UnauthorizedError } from '../ability/abilityHelper';

export async function getSpaceSettingsValues(spaceId: string, searchKey: string) {
export async function getSpaceSettingsValues(
spaceId: string,
searchKey: string,
ability?: Ability,
) {
try {
const { ability } = await getCurrentEnvironment(spaceId);
if (!ability) ({ ability } = await getCurrentEnvironment(spaceId));
return await _getSpaceSettingsValues(spaceId, searchKey, ability);
} catch (e) {
if (e instanceof UnauthorizedError)
Expand Down
42 changes: 41 additions & 1 deletion src/management-system-v2/lib/mcp-utils.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { z } from 'zod';
import { env } from './ms-config/env-vars';
import { getAbilityForUser } from './authorization/authorization';
import { getPairingInfo } from './data/mcp-authorization';
import { isUserErrorResponse } from './user-error';
import { getSpaceSettingsValues } from './data/space-settings';
import { ResourceActionType, ResourceType } from './ability/caslAbility';
import { getMSConfig, getPublicMSConfig } from './ms-config/ms-config';
import { PublicMSConfig } from './ms-config/config-schema';

export const authorizationInfoSchema = {
userCode: z
Expand All @@ -19,6 +22,11 @@ export function toAuthorizationSchema<T extends Record<string, any>>(
}

export async function verifyCode(code: string) {
const msConfig = await getMSConfig();
if (!msConfig.PROCEED_PUBLIC_MCP_ACTIVE) {
throw new Error('MCP feature is disabled.');
}

if (!code) throw new Error('Invalid user code.');

const info = await getPairingInfo(code);
Expand All @@ -31,3 +39,35 @@ export async function verifyCode(code: string) {

return { userId, environmentId, ability };
}

export async function isAccessible(
userId: string,
spaceId: string,
requiredEnvVars: (keyof PublicMSConfig)[] = [],
configValues: string[] = [],
permissions: [ResourceActionType, ResourceType][] = [],
) {
const msConfig = await getPublicMSConfig();
if (requiredEnvVars.some((eV) => !msConfig[eV])) return false;

const ability = await getAbilityForUser(userId, spaceId);

for (const cV of configValues) {
// allow values that are defined with subpath (e.g. 'process-automation.tasklist')
const [settingName, ...path] = cV.split('.');
let settings = await getSpaceSettingsValues(spaceId, settingName, ability);
if (isUserErrorResponse(settings)) return false;
if (settings?.active === false) return false;
let subSetting = settings;
for (let i = 0; i < path.length && !!subSetting; ++i) {
if (subSetting[path[i]]?.active === false) return false;
settings = subSetting[path[i]];
}
Comment on lines +62 to +65
Copy link
Copy Markdown
Contributor

@canptura canptura Apr 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since you're only iterating over the elements of path wouldn't it be more readable with a "for of" iteration?

Suggested change
for (let i = 0; i < path.length && !!subSetting; ++i) {
if (subSetting[path[i]]?.active === false) return false;
settings = subSetting[path[i]];
}
if (!!subSetting){
for (pathElement of path) {
if (subSetting[pathElement]?.active === false) return false;
settings = subSetting[pathElement];
}
}

}

for (const [action, resource] of permissions) {
if (!ability.can(action, resource)) return false;
}

return true;
}
7 changes: 2 additions & 5 deletions src/management-system-v2/lib/ms-config/config-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ export const mSConfigEnvironmentOnlyKeys = [
'NEXTAUTH_SECRET',
'IAM_ORG_USER_INVITATION_ENCRYPTION_SECRET',
'IAM_GUEST_CONVERSION_REFERENCE_SECRET',
'IAM_MCP_ACCESS_ENCRYPTION_SECRET',
'SHARING_ENCRYPTION_SECRET',

'DATABASE_URL',
Expand Down Expand Up @@ -72,6 +71,8 @@ export const msConfigSchema = {
}),
PROCEED_PUBLIC_CONFIG_SERVER_ACTIVE: z.string().default('FALSE').transform(boolParser),

PROCEED_PUBLIC_MCP_ACTIVE: z.string().default('FALSE').transform(boolParser),

NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),

NEXTAUTH_URL: z
Expand All @@ -91,7 +92,6 @@ export const msConfigSchema = {
IAM_ORG_USER_INVITATION_ENCRYPTION_SECRET: z.string().default(''),
SHARING_ENCRYPTION_SECRET: z.string().default(''),
IAM_GUEST_CONVERSION_REFERENCE_SECRET: z.string().default(''),
IAM_MCP_ACCESS_ENCRYPTION_SECRET: z.string().optional().default(''),

PROCEED_PUBLIC_STORAGE_DEPLOYMENT_ENV: z
.enum(['cloud', 'local'])
Expand Down Expand Up @@ -193,9 +193,6 @@ export const msConfigSchema = {
IAM_GUEST_CONVERSION_REFERENCE_SECRET: z
.string()
.default('T8VB/r1dw0kJAXjanUvGXpDb+VRr4dV5y59BT9TBqiQ='),
IAM_MCP_ACCESS_ENCRYPTION_SECRET: z
.string()
.default('d0nb2+Jm1Ur1TQCAFrcH9M1FfRu6bJmL6LkuLslQUBE='),
SCHEDULER_TOKEN: z.string().default('T8VB/r1dw0kJAXjanUvGXpDb+VRr4dV5y59BT9TBqiQ='),
DATABASE_URL: z.string({
required_error: 'DATABASE_URL not in environment variables, try running `yarn dev-ms-db`',
Expand Down
29 changes: 0 additions & 29 deletions src/management-system-v2/prompts/test-prompt.ts

This file was deleted.

53 changes: 0 additions & 53 deletions src/management-system-v2/resources/(processes)/bpmn.ts

This file was deleted.

81 changes: 81 additions & 0 deletions src/management-system-v2/tools/getAccessibleTools.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { type InferSchema } from 'xmcp';
import { isAccessible, toAuthorizationSchema, verifyCode } from '@/lib/mcp-utils';
import { isUserErrorResponse } from '@/lib/user-error';

// Define the schema for tool parameters
export const schema = toAuthorizationSchema({});

// Define tool metadata
export const metadata = {
name: 'get-accessible-tools',
description:
'Get all of the available tools that the current user can access in the current space. Some of the tools that are exposed through MCP might be blocked due to the configuration of the space or the permissions of the user in the space.',
annotations: {
title: 'Get Available Tools',
readOnlyHint: true,
destructiveHint: false,
idempotentHint: true,
},
};

// Tool implementation
export default async function getAvailableTools({ userCode }: InferSchema<typeof schema>) {
try {
const verification = await verifyCode(userCode);

if (isUserErrorResponse(verification)) return `Error: ${verification.error.message}`;

const { userId, environmentId } = verification;

const canAccessProcesses = await isAccessible(
userId,
environmentId,
['PROCEED_PUBLIC_PROCESS_DOCUMENTATION_ACTIVE'],
['process-documentation'],
[['view', 'Process']],
);

let canAccessTasks = await isAccessible(
userId,
environmentId,
['PROCEED_PUBLIC_PROCESS_AUTOMATION_ACTIVE'],
['process-automation.tasklist'],
[['view', 'Task']],
);

let canAccessInstances = await isAccessible(
userId,
environmentId,
['PROCEED_PUBLIC_PROCESS_AUTOMATION_ACTIVE'],
['process-automation.executions'],
[['view', 'Execution']],
);

let canCreateInstances = await isAccessible(
userId,
environmentId,
['PROCEED_PUBLIC_PROCESS_AUTOMATION_ACTIVE'],
['process-automation.executions'],
[['create', 'Execution']],
);

const tools = {
'get-processes': canAccessProcesses,
'get-process-info': canAccessProcesses,
'start-process': canCreateInstances,
'get-organization-data': true,
'get-user-data': true,
};

const result = Object.entries(tools)
.filter(([_, accessible]) => accessible)
.map(([name]) => name);

return {
content: [{ type: 'text', text: JSON.stringify(result) }],
};
} catch (err) {
if (err instanceof Error) return err.message;
else return 'Error: Something went wrong';
}
}
Loading
Loading