Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
b7023ce
Implement MCP server routes, feature flags, and related tests
cstns Jun 22, 2026
c413c16
fix failing tests by replacing `null` with empty string for PAT creat…
cstns Jun 23, 2026
6b598c8
fix failing unit test
cstns Jun 23, 2026
1ec0bb6
Merge remote-tracking branch 'origin/main' into 7426_mcp-scaffolding
cstns Jun 26, 2026
a546212
feat(expert): human-in-the-loop tool permissions (#421)
Jun 30, 2026
bdb36fb
refactor(expert): align tool-permissions UI with FlowFuse patterns
Jun 30, 2026
0ef2ef6
feat(expert): show tool permissions everywhere; split flow-building a…
Jun 30, 2026
ff6ee80
fix(expert): tidy tool approval card
Jun 30, 2026
3368791
Merge branch 'feat/408-expert-plan-mode' into feat/421-expert-tool-pe…
Jun 30, 2026
438f60c
Merge branch 'feat/408-expert-plan-mode' into feat/421-expert-tool-pe…
Jun 30, 2026
dd6366c
fix(expert): extend chat session lifetime to 30 minutes
Jun 30, 2026
741d0ff
fix(expert): keep chat session lifetime at 28 minutes
Jun 30, 2026
f3502e8
feat(expert): render tool approval payload as prettified JSON
Jun 30, 2026
29493e6
feat(expert): crash-proof tool approval payload + collapse on decision
Jun 30, 2026
a4c44ae
refactor(expert): simplify JSON payload fallback to a plain error
Jun 30, 2026
462da8d
feat(expert): real chevron on payload toggle + unwrap approval card
Jun 30, 2026
3afa8c2
Merge branch 'main' into 7426_mcp-scaffolding
cstns Jul 1, 2026
503263b
Fix RBAC permission check for inflight messages (#7641)
Steve-Mcl Jul 1, 2026
66f87ec
docs: Fix team type names on the `Static asset service` page (#7643)
ppawlowski Jul 1, 2026
6ffa250
Merge branch 'feat/408-expert-plan-mode' into feat/421-expert-tool-pe…
Jul 1, 2026
1b7ed4d
docs(expert): trim redundant tool-permission comments
Jul 1, 2026
0e7ba7a
feat(expert): per-team tool permissions, session-scoped approvals, se…
Jul 1, 2026
5511bf1
fix(expert): per-group tool defaults, session-scoped approvals, toggl…
Jul 1, 2026
11a01cb
feat(expert): reset per-scope tool permissions and surface individual…
Jul 1, 2026
c70a5f5
[7644] Dropdowns that allow manual input will not accept inputs that …
n-lark Jul 1, 2026
88f7582
[7441] Consolidate broker credential generation (#7642)
n-lark Jul 1, 2026
0c9b327
Insights for devices and self hosted platforms (#7604)
Steve-Mcl Jul 1, 2026
484652e
Merge branch 'main' into 7426_mcp-scaffolding
cstns Jul 1, 2026
80fed90
test(expert): cover tool-permission catalog, store and approval flow …
Jul 1, 2026
50de406
fix(expert): reflect external approval outcomes on the card (#421)
Jul 1, 2026
adcafae
Modularize MCP server by implementing tool definitions, stateless req…
cstns Jul 1, 2026
97cfb22
merge: bring MCP scaffolding (#7596) into HITL branch for PlatformAut…
Jul 1, 2026
0a6963b
feat(expert): merge platform tools into the HITL permissions catalog …
Jul 1, 2026
47a4f57
feat(expert): stateless human-in-the-loop tool approvals (#421)
Jul 1, 2026
f6ceec4
MCP platform server scaffolding (#7596)
cstns Jul 2, 2026
3004c80
Merge remote-tracking branch 'origin/main' into feat/421-expert-tool-…
Jul 2, 2026
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 docs/install/kubernetes/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,7 @@ postgresql:

Apply changes with [platform startup command](#start-flowfuse-platform).

Check the [FlowFuse Helm chart documentation](https://github.com/FlowFuse/helm/tree/main/helm/flowforge#postgresql) for more details about the parameters that can be configured for the PostgreSQL database.
Check the [FlowFuse Helm chart documentation](https://github.com/FlowFuse/helm/tree/main/helm/flowfuse#postgresql) for more details about the parameters that can be configured for the PostgreSQL database.

### How to backup embedded database?

Expand Down
2 changes: 1 addition & 1 deletion docs/user/static-asset-service.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ The Static Asset Service allows you to store files permanently within your FlowF
### FlowFuse Cloud

- A Instance Stack with a launcher version of 2.8.0 or greater.
- Team or Enterprise Team Type.
- Pro or Enterprise Team Type.

### Self-Hosted

Expand Down
112 changes: 101 additions & 11 deletions forge/comms/aclManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
* Other components (ie EE-specific features) can register their own additional ACLs
*/
module.exports = function (app) {
const expertRbacToolCheck = async (teamMembership, application, toolName) => {
const expertRbacToolCheck = async (teamMembership, toolName, application) => {
const applicationCheck = typeof application !== 'undefined'
const applicationHash = typeof application === 'object' ? application.hashid : application
if (toolName === 'expert:status-message') {
return true
Expand All @@ -20,8 +21,15 @@ module.exports = function (app) {
'automation:get-flows': 'project:flows:view'
}
const requiredPermission = toolAccessPermission[toolName] || 'project:flows:edit' // default to highest level of access if tool isn't in the list, to be safe
if (!app.hasPermission(teamMembership, requiredPermission, { applicationId: applicationHash })) {
return false

if (applicationCheck) {
if (!app.hasPermission(teamMembership, requiredPermission, { applicationId: applicationHash })) {
return false
}
} else {
if (!app.hasPermission(teamMembership, requiredPermission)) {
return false
}
}
return true
}
Expand Down Expand Up @@ -163,6 +171,81 @@ module.exports = function (app) {
return false
}
},
checkExpertPlatformTopic: async function (topicParts, usernameParts, acl) {
// topicParts = [ fullTopic , <userid>, <sessionid>, <command> ]
// usernameParts = [ 'forge_platform' | 'expert-agent', <userid> [, <sessionid>] ]

const ValidationError = function (message) {
const error = new Error(message)
error.name = 'ACLValidationError'
return error
}

try {
const [, userId, sessionId, command] = topicParts
if (!userId || !sessionId || !command) {
throw ValidationError('invalid topic format')
}

if (command === '+') {
if (!acl.allowWildcard?.command) {
throw ValidationError('invalid command wildcard')
}
} else {
// validate command format is [forge:<command>]|[insights:<command>]
const commandParts = command.split(':', 2)
if (commandParts.length !== 2) {
throw ValidationError('invalid command format')
}
const [commandAgent, commandName] = commandParts
switch (commandAgent) {
case 'automation':
if (['mcp-get-features', 'mcp-call-tool'].indexOf(commandName) === -1) {
throw ValidationError('invalid platform command for platform api')
}
break
case 'insights':
if (['mcp-call-tool', 'mcp-read-resource'].includes(commandName) === false) {
throw ValidationError('invalid platform command for insights')
}
break
default:
throw ValidationError('invalid platform command')
}
}

// at minimum, ensure session is present and either a wildcard or 8 or more chars
if (sessionId === '+') {
if (!acl.allowWildcard?.session) {
throw ValidationError('invalid session wildcard')
}
} else if (sessionId.length < 8) {
throw ValidationError('invalid session id')
}

if (userId === '+') {
if (!acl.allowWildcard?.user) {
throw ValidationError('invalid user wildcard')
}
} else {
const user = await app.db.models.User.byId(userId)
if (!user || user.suspended) {
throw ValidationError('invalid user')
}
// TODO: consider checking if the user has permission to operate on this channel here or in the command handler.
// For now, we just check the user exists and is not suspended.
}

return true
} catch (err) {
if (err.name === 'ACLValidationError') {
app.log.warn(`ACL validation error for expert platform topic: ${err.message}`)
} else {
app.log.error(`Unexpected error during ACL validation for expert platform topic: ${err.message}`)
}
}
return false
},
checkExpertTopic: async function (topicParts, usernameParts, acl) {
// topicParts = [ fullTopic , <userid>, <sessionid>, <entityType>, <entityId> [, <inflightType>] ]
// usernameParts = [ 'expert-client' | 'expert-agent', <userid> [, <sessionid>] ]
Expand Down Expand Up @@ -265,7 +348,6 @@ module.exports = function (app) {
throw ValidationError('team does not exist')
} else {
teamId = team.id
applicationHash = null // NA
}
} else {
throw ValidationError('invalid entity')
Expand Down Expand Up @@ -293,7 +375,7 @@ module.exports = function (app) {

// if this is an inflight channel messages we must validate the user has appropriate RBAC permission
if (isInflight) {
const result = await expertRbacToolCheck(teamMembership, applicationHash, inflightType)
const result = await expertRbacToolCheck(teamMembership, inflightType, applicationHash)
if (!result) {
throw ValidationError('user does not have permission to access this inflight topic')
}
Expand Down Expand Up @@ -336,7 +418,9 @@ module.exports = function (app) {
// ff/v1/platform/sync
{ topic: /^ff\/v1\/platform\/sync$/ },
// ff/v1/platform/leader
{ topic: /^ff\/v1\/platform\/leader$/ }
{ topic: /^ff\/v1\/platform\/leader$/ },
// platform can listen for Expert Agent requests
{ topic: /^ff\/v1\/expert\/([^/]+)\/([^/]+)\/platform\/([^/]+)\/request$/, verify: 'checkExpertPlatformTopic', allowWildcard: { user: true, session: true, command: true }, isPlatform: true, isSub: true, agent: 'platform' }
],
pub: [
// Send commands to project launchers
Expand All @@ -363,7 +447,9 @@ module.exports = function (app) {
// ff/v1/platform/sync
{ topic: /^ff\/v1\/platform\/sync$/ },
// ff/v1/platform/leader
{ topic: /^ff\/v1\/platform\/leader$/ }
{ topic: /^ff\/v1\/platform\/leader$/ },
// platform can respond to Expert Agent requests
{ topic: /^ff\/v1\/expert\/([^/]+)\/([^/]+)\/platform\/([^/]+)\/response$/, verify: 'checkExpertPlatformTopic', isPlatform: true, isPub: true, agent: 'platform' }
]
},
project: {
Expand Down Expand Up @@ -451,12 +537,16 @@ module.exports = function (app) {
// backend client (agent)
expertAgent: {
sub: [
{ topic: /^ff\/v1\/expert\/([^/]+)\/([^/]+)\/([^/]+)\/([^/]+)\/support\/chat\/request$/, verify: 'checkExpertTopic', channel: 'chat', allowWildcard: { user: true, session: true, entity: true }, isAgent: true, isSub: true },
{ topic: /^ff\/v1\/expert\/([^/]+)\/([^/]+)\/([^/]+)\/([^/]+)\/support\/inflight\/([^/]+)\/response$/, verify: 'checkExpertTopic', channel: 'inflight', allowWildcard: { user: true, session: true, entity: true, inflightType: true }, isAgent: true, isSub: true }
{ topic: /^ff\/v1\/expert\/([^/]+)\/([^/]+)\/([^/]+)\/([^/]+)\/support\/chat\/request$/, verify: 'checkExpertTopic', channel: 'chat', allowWildcard: { user: true, session: true, entity: true }, isAgent: true, isSub: true, agent: 'support' },
{ topic: /^ff\/v1\/expert\/([^/]+)\/([^/]+)\/([^/]+)\/([^/]+)\/support\/inflight\/([^/]+)\/response$/, verify: 'checkExpertTopic', channel: 'inflight', allowWildcard: { user: true, session: true, entity: true, inflightType: true }, isAgent: true, isSub: true, agent: 'support' },
// Expert agent can listen for platform responses
{ topic: /^ff\/v1\/expert\/([^/]+)\/([^/]+)\/platform\/([^/]+)\/response$/, verify: 'checkExpertPlatformTopic', allowWildcard: { user: true, session: true, command: true }, isAgent: true, isSub: true, agent: 'platform' }
],
pub: [
{ topic: /^ff\/v1\/expert\/([^/]+)\/([^/]+)\/([^/]+)\/([^/]+)\/support\/chat\/response$/, verify: 'checkExpertTopic', channel: 'chat', isAgent: true, isPub: true },
{ topic: /^ff\/v1\/expert\/([^/]+)\/([^/]+)\/([^/]+)\/([^/]+)\/support\/inflight\/([^/]+)\/request$/, verify: 'checkExpertTopic', channel: 'inflight', isAgent: true, isPub: true }
{ topic: /^ff\/v1\/expert\/([^/]+)\/([^/]+)\/([^/]+)\/([^/]+)\/support\/chat\/response$/, verify: 'checkExpertTopic', channel: 'chat', isAgent: true, isPub: true, agent: 'support' },
{ topic: /^ff\/v1\/expert\/([^/]+)\/([^/]+)\/([^/]+)\/([^/]+)\/support\/inflight\/([^/]+)\/request$/, verify: 'checkExpertTopic', channel: 'inflight', isAgent: true, isPub: true, agent: 'support' },
// Expert agent can respond to platform requests
{ topic: /^ff\/v1\/expert\/([^/]+)\/([^/]+)\/platform\/([^/]+)\/request$/, verify: 'checkExpertPlatformTopic', isAgent: true, isPub: true, agent: 'platform' }
]
}
}
Expand Down
Loading
Loading