Skip to content

feat: add MCP Apps (SEP-1865) support to platform and functions SDK#2061

Open
danielkov wants to merge 11 commits intomainfrom
daniel/feat-mcp-app-support-sep-1865
Open

feat: add MCP Apps (SEP-1865) support to platform and functions SDK#2061
danielkov wants to merge 11 commits intomainfrom
daniel/feat-mcp-app-support-sep-1865

Conversation

@danielkov
Copy link
Copy Markdown
Contributor

@danielkov danielkov commented Apr 1, 2026

Summary

  • builds on Sagar's changes from feat: add MCP Apps (SEP-1865) support to platform and functions SDK #1788
  • add MCP App support across the playground, functions SDK, seed flow, and local runner
  • include follow-up fixes for runner lifecycle, deployment-scoped resource resolution, and non-local seed behavior
  • clean up merge-only churn and align the SDK MIME expectations with the final MCP App content type
Screen.Recording.2026-04-01.at.12.16.03.mov

Runner-side changes

  • local functions no longer assume a fixed :8888 port: the runner now binds a listener, writes the chosen address to an addr file, and the local orchestrator proxies to that live process
  • local function calls now fail fast when the child process exits before opening its FIFO response pipe, instead of hanging until timeout
  • the local backend wiring in server/cmd/gram/deps.go now instantiates the new LocalRunner orchestrator, which launches runner processes and proxies tool/resource traffic through them
  • the JS entrypoint bootstrap now tolerates basename-invoked startup paths so local runner execution still calls main() when launched from copied script paths

Context

simplesagar and others added 4 commits March 5, 2026 21:50
Add resource declaration capabilities to the Gram Functions SDK,
enabling function authors to declare UI resources and link tools to
them following the MCP Apps specification.

- Add resource(), uiResource(), and handleResourceRead() to Gram class
- Add meta field to ToolDefinition for tool-to-UI linking
- Update manifest() to include resources and tool meta
- Update extend() to merge resources across Gram instances
- Add resources capability to fromGram() MCP server conversion
- Align ManifestToolV0.Meta type with ManifestResourceV0.Meta (map[string]any)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Without this, tools/list from fromGram()-created MCP servers would not
include _meta (e.g. ui/resourceUri), making it impossible for MCP Apps
hosts to discover tool-to-UI resource links.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ata() helper

uiResource() now provides genuine convenience beyond setting the MIME type:
- Auto-generates `ui://{name}` URI when `uri` is omitted
- Accepts `body` + `styles` to wrap in HTML scaffold with Gram.onData() helper
- Falls back to raw `content` mode for full control

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@danielkov danielkov requested a review from a team as a code owner April 1, 2026 10:52
@vercel
Copy link
Copy Markdown

vercel bot commented Apr 1, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
gram-docs-redirect Ready Ready Preview, Comment Apr 1, 2026 7:34pm

Request Review

Copy link
Copy Markdown

@claude claude bot left a comment

Choose a reason for hiding this comment

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

Claude Code Review

This repository is configured for manual code reviews. Comment @claude review to trigger a review and subscribe this PR to future pushes, or @claude review once for a one-time review.

Tip: disable this comment in your organization's Code Review settings.

@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Apr 1, 2026

🦋 Changeset detected

Latest commit: 97af11b

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 4 packages
Name Type
dashboard Minor
server Patch
@gram-ai/functions Minor
@gram-ai/create-function Minor

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

Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 3 potential issues.

View 7 additional findings in Devin Review.

Open in Devin Review

Comment on lines +202 to +204
const manifest = g.manifest();
const hasResources =
manifest.resources != null && manifest.resources.length > 0;
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.

🚩 fromGram() captures manifest snapshot once, won't reflect later tool/resource additions

In ts-framework/functions/src/mcp.ts:202, const manifest = g.manifest() is called once when fromGram() executes. The ListToolsRequestSchema and ListResourcesRequestSchema handlers at lines 209 and 302 use this captured snapshot. If tools or resources are added to the Gram instance after fromGram() is called, they won't be reflected. This appears intentional — fromGram() creates a server from a point-in-time snapshot — but it's a subtle contract worth documenting.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

const shouldSeedFunctions = functionsProvider === "local";
if (!shouldSeedFunctions) {
log.info(
`Skipping seeded MCP app function assets because GRAM_FUNCTIONS_PROVIDER is '${functionsProvider}', not 'local'.`,
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

TODO: make this work with flyio

devin-ai-integration[bot]

This comment was marked as resolved.

Comment on lines +1 to +6
---
"dashboard": patch
"server": patch
"@gram-ai/functions": patch
---

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.

Suggested change
---
"dashboard": patch
"server": patch
"@gram-ai/functions": patch
---
---
"dashboard": minor
"server": patch
"@gram-ai/functions": minor
---

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.

Can you test this out on dev before going to to prod? Specifically interested in CSP abuses.

Comment on lines +607 to +670
func invCheckLocalDeploy(req RunnerDeployRequest) error {
switch {
case req.ProjectID == uuid.Nil:
return errors.New("project id cannot be nil")
case req.DeploymentID == uuid.Nil:
return errors.New("deployment id cannot be nil")
case req.FunctionID == uuid.Nil:
return errors.New("function id cannot be nil")
case req.AccessID == uuid.Nil:
return errors.New("access id cannot be nil")
case !IsSupportedRuntime(string(req.Runtime)):
return fmt.Errorf("unsupported runtime: %s", req.Runtime)
case len(req.Assets) == 0:
return errors.New("deployment assets cannot be empty")
case req.Assets[0].AssetURL == nil:
return errors.New("deployment asset url cannot be nil")
case req.BearerSecret == "":
return errors.New("bearer secret cannot be empty")
default:
return nil
}
}

func invCheckLocalToolCall(req RunnerToolCallRequest) error {
switch {
case req.OrganizationID == "":
return errors.New("organization id cannot be empty")
case req.OrganizationSlug == "":
return errors.New("organization slug cannot be empty")
case req.ProjectID == uuid.Nil:
return errors.New("project id cannot be nil")
case req.DeploymentID == uuid.Nil:
return errors.New("deployment id cannot be nil")
case req.FunctionsID == uuid.Nil:
return errors.New("functions id cannot be nil")
case req.ToolURN.IsZero():
return errors.New("tool urn cannot be empty")
case req.ToolName == "":
return errors.New("tool name cannot be empty")
default:
return nil
}
}

func invCheckLocalReadResource(req RunnerResourceReadRequest) error {
switch {
case req.OrganizationID == "":
return errors.New("organization id cannot be empty")
case req.OrganizationSlug == "":
return errors.New("organization slug cannot be empty")
case req.ProjectID == uuid.Nil:
return errors.New("project id cannot be nil")
case req.DeploymentID == uuid.Nil:
return errors.New("deployment id cannot be nil")
case req.FunctionsID == uuid.Nil:
return errors.New("functions id cannot be nil")
case req.ResourceURN.IsZero():
return errors.New("resource urn cannot be empty")
case req.ResourceURI == "":
return errors.New("resource uri cannot be empty")
default:
return nil
}
}
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.

use inv.Check for these

@danielkov danielkov added the preview Spawn a preview environment label Apr 1, 2026
@speakeasybot
Copy link
Copy Markdown
Collaborator

speakeasybot commented Apr 1, 2026

🚀 Preview Environment (PR #2061)

Preview URL: https://pr-2061.dev.getgram.ai

Component Status Details Updated (UTC)
✅ Database Ready Existing database reused 2026-04-01 19:40:51.
✅ Images Available Container images ready 2026-04-01 19:40:34.

Gram Preview Bot

Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 2 new potential issues.

View 16 additional findings in Devin Review.

Open in Devin Review


l.binaryPath = filepath.Join(binDir, localRunnerBinaryName)
//nolint:gosec // builds the checked-out local runner from a fixed repo-relative package path.
cmd := exec.CommandContext(ctx, "go", "build", "-o", l.binaryPath, "./functions/cmd/runner")
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.

🔴 sync.Once with request-scoped context permanently poisons runner binary build on cancellation

In ensureRunnerBinary, the ctx parameter is the caller's request context (from invokeLocalRunnerhandleLocalProxyRequestr.Context()). The go build command uses exec.CommandContext(ctx, ...), so if the first HTTP request that triggers the build is canceled (e.g., client disconnects) while the build is in progress, the build fails with context cancellation. Because sync.Once guarantees the function runs only once, l.binaryErr is permanently set to the cancellation error. All subsequent ToolCall and ReadResource calls will fail forever, even though the context cancellation was transient and unrelated to the build itself. The process must be restarted to recover.

Suggested change
cmd := exec.CommandContext(ctx, "go", "build", "-o", l.binaryPath, "./functions/cmd/runner")
cmd := exec.CommandContext(context.Background(), "go", "build", "-o", l.binaryPath, "./functions/cmd/runner")
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +383 to +389
env := append(os.Environ(),
"GRAM_SERVER_URL="+l.serverURL.String(),
functionAuthSecretVar+"="+deployment.BearerSecret,
"GRAM_PROJECT_ID="+deployment.ProjectID,
"GRAM_DEPLOYMENT_ID="+deployment.DeploymentID,
"GRAM_FUNCTION_ID="+deployment.FunctionID,
)
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.

🚩 Parent process environment leaked to local runner subprocess

At deploy_local.go:383-389, os.Environ() passes the entire parent process environment to the runner subprocess, including potentially sensitive variables (database URLs, API keys, etc.). While this is a dev-only local runner where the parent and child share the same trust domain, the runner's user code (functions.js) executes in the same process and could read these environment variables. The Fly runner uses isolated VMs with explicit secrets management.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

preview Spawn a preview environment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants