Skip to content
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]];
}
}

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