feat: resolve MCP servers and skills from x-tfg-mcp and x-tfg-skills - #592
feat: resolve MCP servers and skills from x-tfg-mcp and x-tfg-skills#592pranjaltrvd wants to merge 2 commits into
Conversation
In TrueFoundry mode a request can define the MCP servers and skills its agent spec names, instead of them being configured in the tenant's registry. The definitions take precedence for that request only and are never persisted, so they stay out of tenant settings and the gateway's metrics, and a rotating credential can ride each turn rather than being stored somewhere it goes stale. Both resolve through the store seam the spec validator and the turn resolver already use, so no call site learns about the headers. Inline MCP credentials come from the manifest's own auth.headers and reach the upstream unchanged -- no caller Bearer is layered over them. An unfiltered list still returns only configured resources. The skill store becomes request-scoped to carry this, mirroring the model and MCP resolvers. Co-authored-by: Cursor <cursoragent@cursor.com>
🦋 Changeset detectedLatest commit: 0608835 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 2 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 0da6f6c. Configure here.
| return undefined; | ||
| } | ||
| const now = new Date().toISOString(); | ||
| return { id: name, tenant_id, name, manifest, created_at: now, updated_at: now }; |
There was a problem hiding this comment.
Inline lookup hits inherited prototype keys
Low Severity
Bracket lookup on the plain #inline map treats inherited Object.prototype keys as request-scoped resources. constructor is a valid NameSchema name, so a by-name get or filtered list can return a fake record whose manifest is the Object function instead of falling through to the registry.
Additional Locations (2)
Reviewed by Cursor Bugbot for commit 0da6f6c. Configure here.
| raw: string, | ||
| header: string, | ||
| parseEntry: (name: string, definition: object) => EntryResult<TManifest>, | ||
| ): Readonly<Record<string, TManifest>> { |
There was a problem hiding this comment.
Helpers take two positional strings
Low Severity
New helpers pass two string arguments positionally: parseByName takes raw and header, and both overlay #toRecord methods take tenant_id and name. Repo rules require a single options object when more than one parameter shares a type, so a swap is type-silent.
Additional Locations (2)
Triggered by project rule: TrueForge review rules
Reviewed by Cursor Bugbot for commit 0da6f6c. Configure here.
sfy-server builds the spec and the two headers in its own repo, so nothing checked that the two agreed. This runs its exact output through AgentSpecSchema and both inline parsers, and holds the header block to ASCII within one 8KB ingress buffer. Co-authored-by: Cursor <cursoragent@cursor.com>


In TrueFoundry mode, a request can now define the MCP servers and skills its agent spec names, rather than requiring them to be configured in the tenant's registry first.
Why
An embedded assistant ships its own agent definition. Registering its MCP servers in the tenant's registry to make that definition resolvable puts them in the tenant's settings and its gateway metrics, where they are noise at best and confusing at worst — they are not the tenant's servers and nobody there can meaningfully manage them.
Skills have the same problem with a sharper edge:
PUT /settings/skillsis admin-gated, so a least-privilege service identity cannot register one at all.What
Two headers, each a JSON object keyed by resource name, mirroring
x-tfg-mcp-headers:typeandnameare implied and filled in during parsing, so what a caller sends is the part that is actually theirs to specify.Definitions are request-scoped and never persisted. They take precedence over a registry entry of the same name for that request only.
How
Both resolve through the store seam that
validateAgentSpecand the turn resolver already use, so no call site learns that the headers exist.resolveMcpServerStore(c)already took aContext; the skill store did not, so it becomesresolveSkillStore(c)to match the model and MCP resolvers. That rename is most of the diff.Two behaviours worth calling out:
auth.headers, with no caller Bearer layered over them. That is what lets a rotating token ride each turn instead of being stored somewhere it goes stale six hours later.dcrauth is rejected on an inline server: it needs a registered client and a stored token, neither of which a per-request definition has.Testing
23 unit tests covering parsing, rejection cases, precedence, credential passthrough, and the unfiltered-list boundary. Full unit suite, both test tsconfigs and lint are clean.
Note
Medium Risk
Changes turn-time MCP/skill resolution and accepts client-supplied URLs and credentials in headers, though scoped to TrueFoundry mode with validation and no persistence.
Overview
In TrueFoundry mode, callers can supply MCP server and skill definitions per request via
x-tfg-mcpandx-tfg-skills(JSON maps keyed by name). Those definitions are not persisted, override the tenant registry for that request only, and are used for agent spec validation and turn execution the same way as configured resources.skillStorebecomesresolveSkillStore(c)across routers and wiring so skills resolve per request like MCP and agents. When the headers are present,InlineMcpServerStore/InlineSkillStoredecorate the inner stores: by-name lookup and name-filtered lists merge inline + registry; unfiltered lists stay registry-only so request-scoped resources do not appear in settings or pickers. Inline MCPauth.headersare sent upstream as-is (no caller Bearer layered on), supporting rotating tokens; DCR auth is rejected for inline MCP.Parsing validates manifests (with implied
type/name) and returns 400 on malformed headers rather than silent registry fallback. New unit tests cover parsing, store overlay behavior, and the Ask TFY wire contract (ASCII headers, size bounds).Reviewed by Cursor Bugbot for commit 0608835. Bugbot is set up for automated code reviews on this repo. Configure here.