Skip to content

Inject source context headers in MCP tool calls#7830

Merged
cstns merged 11 commits into
mainfrom
feat/7563-mcp-audit-trail
Jul 17, 2026
Merged

Inject source context headers in MCP tool calls#7830
cstns merged 11 commits into
mainfrom
feat/7563-mcp-audit-trail

Conversation

@cstns

@cstns cstns commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Description

Wires up the MCP platform automation endpoint to use the source context infrastructure from #7562, so expert agent tool calls are recorded as mcp:expert in the audit trail.

  • platformAutomation.js mints a nonce per app.inject() call via app.nonceStore.createSourceNonce() with source: mcp:expert and the tool name.
  • Audit log UI (AuditEntry.vue) shows a sparkles icon next to the username for MCP-sourced entries and a terminal icon for API calls. Tooltip includes the source and tool name (e.g. "via Expert (Tool name: create-instance)").
image image image

Related Issue(s)

closes #7563

Checklist

  • I have read the contribution guidelines
  • Suitable unit/system level tests have been added and they pass
  • Documentation has been updated
    • Upgrade instructions
    • Configuration details
    • Concepts
  • Changes flowforge.yml?
    • Issue/PR raised on FlowFuse/helm to update ConfigMap Template
    • Issue/PR raised on FlowFuse/CloudProject to update values for Staging/Production
  • Link to Changelog Entry PR, or note why one is not needed.

Labels

  • Includes a DB migration? -> add the area:migration label

@cstns cstns self-assigned this Jul 15, 2026
@cstns
cstns requested review from andypalmi and knolleary July 15, 2026 11:23
@cstns
cstns marked this pull request as draft July 15, 2026 11:23
@codecov

codecov Bot commented Jul 15, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 75.56%. Comparing base (19e0c9a) to head (7b21f97).

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #7830      +/-   ##
==========================================
+ Coverage   75.23%   75.56%   +0.33%     
==========================================
  Files         432      432              
  Lines       22973    22975       +2     
  Branches     6090     6090              
==========================================
+ Hits        17284    17362      +78     
+ Misses       5689     5613      -76     
Flag Coverage Δ
backend 75.56% <100.00%> (+0.33%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@andypalmi

Copy link
Copy Markdown
Contributor

Would it be worth adding some test coverage here? Assuming the resolveSourceContext tests land in #7825, this PR could probably cover the two new pieces:

  • a unit test for the inject behavior in platformAutomation.js, checking a tool call mints a nonce with source: 'mcp:expert' and the right toolName and sends it as x-ff-source-nonce
  • an integration test for the full path: drive a platform automation tool call and assert the resulting audit entry has source: 'mcp:expert' and the expected toolName

That would close the loop on the audit path end to end. Happy to help write these if it's useful.

@andypalmi

Copy link
Copy Markdown
Contributor

Small question on the nonce metadata: was leaving out tokenId intentional here? The nonce carries source and toolName but not the token id:

const nonce = this.app.nonceStore.createSourceNonce({
    source: 'mcp:expert',
    toolName
})

#7563 mentions the token identifier as part of the source context, and the #7562 infra already carries tokenId through into the audit body, so the plumbing is there. It looks like the main blocker is that getOrCreateMcpPlatformToken only returns { token } and not the AccessToken id, so there's nothing to pass yet.

If that's the reason, would it be worth surfacing the id from that helper and setting tokenId on the nonce? It becomes more useful once third-party mcp agents (each with their own PAT) arrive and you want to tell them apart. Not blocking, just wanted to check the thinking.

@andypalmi

Copy link
Copy Markdown
Contributor

Minor one on the SCSS: dropping display: block makes sense for the flex icon layout. I traced the grid and it still renders correctly in both the mobile and large layouts, since the DOM order (time, info, trigger) lines up with the grid-template-areas order and .ff-audit-entry-info is the one explicitly placed cell.

The only thing is that removing the grid-area assignments means placement now relies on auto-placement matching DOM order, so a future reorder could shift things without warning. Would it be worth keeping the two grid-area declarations just to make it explicit? They don't conflict with the new flex. Totally optional.

@andypalmi

Copy link
Copy Markdown
Contributor

Not related to this PR, just spotted it while reading nearby: in the tamper check just above, tool.annotations is read before the if (!tool) guard, so an unknown tool name would throw a raw error instead of the friendly MCP_PLATFORM_TOOL_NOT_FOUND. Might be worth reordering the null check at some point. Feel free to ignore for now since it predates this change.

@andypalmi

Copy link
Copy Markdown
Contributor

Nice work on this, the nonce wiring looks clean. A few things I liked while reviewing:

  • The nonce is minted fresh per inject() call, which is right for single-use, and it's the header the base branch's resolveSourceContext consumes.
  • The old x-ff-automation-source: expert header is fully removed with no leftover readers.
  • The metadata passed to createSourceNonce is a fresh object each call, so the mutation note from Add source column to AuditLog with request context propagation #7825 doesn't apply here.
  • Frontend reads entry.source and entry.body?.sourceContext?.toolName, matching the view and formatter output from 3rd Party MCP - Audit Source Column + Request Context Propagation #7562.
  • The source === 'mcp' UI branch being ready for third-party agents ahead of time is a nice touch.

Nothing blocking from me, just the couple of questions above, but I really think we should add tests.

@cstns
cstns marked this pull request as ready for review July 16, 2026 12:16
@cstns
cstns marked this pull request as draft July 16, 2026 12:17
@cstns

cstns commented Jul 16, 2026

Copy link
Copy Markdown
Contributor Author

Logging/exposing the token doesn't make sense for the first-party expert agent (short-lived, nameless, user-invisible tokens). The scaffolding for token name is already in place via the PAT path in resolveSourceContext for when third-party MCP agents arrive.

@cstns
cstns marked this pull request as ready for review July 16, 2026 12:49

@andypalmi andypalmi left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Looks good to me

Base automatically changed from feat/7562-audit-source-column to main July 17, 2026 11:03
@cstns
cstns enabled auto-merge (squash) July 17, 2026 12:25
@cstns
cstns merged commit 9209878 into main Jul 17, 2026
36 of 38 checks passed
@cstns
cstns deleted the feat/7563-mcp-audit-trail branch July 17, 2026 12:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3rd Party MCP - Inject Audit Headers in MCP Tool Calls

2 participants