@construct-computer/app-sdk
v0.2.6
Published
SDK for building Construct apps — MCP server helper, TypeScript types, and dev tools
Maintainers
Readme
@construct-computer/app-sdk
SDK for building Construct apps — MCP server helper, TypeScript types, and manifest validation.
Quick Start
pnpm add @construct-computer/app-sdkServer (server.ts)
import { ConstructApp } from '@construct-computer/app-sdk';
const app = new ConstructApp({ name: 'my-app', version: '1.0.0' });
app.tool('greet', {
description: 'Say hello to someone',
parameters: {
name: { type: 'string', description: 'Who to greet' },
},
handler: async (args) => `Hello, ${args.name}!`,
});
// That's it — export as a Cloudflare Worker
export default app;export default app handles everything automatically:
POST /mcp— MCP JSON-RPC endpoint (tool calls, tool listing, initialization)GET /health— health check for the Construct desktop- Static asset serving via the Cloudflare
ASSETSbinding (if configured inwrangler.toml) /ui/*path rewriting to/*(so dev matches the published URL structure)- CORS headers on every response (required for Construct desktop dev mode)
wrangler.toml
name = "my-construct-app"
main = "server.ts"
compatibility_date = "2024-12-01"
[assets]
directory = "./ui"
binding = "ASSETS"
not_found_handling = "none"
run_worker_first = ["/*"]The run_worker_first = ["/*"] setting ensures all requests hit your server first, so /mcp and /health are handled by the SDK before falling through to static assets.
UI (ui/index.html)
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>My App</title>
<link rel="stylesheet" href="https://registry.construct.computer/sdk/construct.css">
<script src="https://registry.construct.computer/sdk/construct.js"></script>
</head>
<body>
<h1>My App</h1>
<button id="greet-btn">Greet</button>
<div id="output"></div>
<script src="app.js"></script>
</body>
</html>The construct.js and construct.css script/link tags are required in your HTML. The Construct desktop strips them at load time and injects its own bridge that exposes the construct.* APIs.
UI Types (ui/construct.d.ts)
Copy src/construct-global.d.ts into your ui/ directory for full autocomplete in your UI code:
/// <reference path="./construct.d.ts" />
construct.ready(() => {
construct.ui.setTitle('My App');
document.getElementById('greet-btn').addEventListener('click', async () => {
const result = await construct.tools.callText('greet', { name: 'World' });
document.getElementById('output').textContent = result;
});
});Pair with a jsconfig.json in ui/ for VS Code autocomplete:
{
"compilerOptions": {
"target": "ES2022",
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"checkJs": true
}
}Manifest (manifest.json)
Add $schema for IDE validation:
{
"$schema": "https://raw.githubusercontent.com/construct-computer/app-sdk/main/schemas/manifest.schema.json",
"name": "My App",
"description": "What it does in one sentence.",
"author": { "name": "Your Name" },
"icon": "ui/icon.svg",
"categories": ["utilities"],
"tags": ["my-tag"],
"ui": {
"entry": "ui/index.html",
"width": 480,
"height": 600
}
}App Structure
my-construct-app/
├── manifest.json # App metadata (required)
├── server.ts # MCP server entry point (required)
├── wrangler.toml # Cloudflare Workers config
├── tsconfig.json # TypeScript config (server)
├── package.json
├── ui/ # Visual interface (optional — omit for tools-only apps)
│ ├── index.html # UI entry point
│ ├── app.js # UI logic
│ ├── construct.d.ts # SDK type declarations (copy from app-sdk)
│ ├── jsconfig.json # JS project config (enables autocomplete)
│ └── icon.svg # App icon (256x256, PNG/SVG/JPG)
└── screenshots/ # Optional store screenshots
└── 1.pngAPI Reference
ConstructApp
The main class. Handles JSON-RPC 2.0 routing, tool registration, auth extraction, asset serving, and CORS.
import { ConstructApp } from '@construct-computer/app-sdk';
const app = new ConstructApp({ name: string, version: string });
// Register tools
app.tool('name', {
description: 'What the AI sees when deciding whether to use this tool',
parameters: {
input: { type: 'string', description: 'The input value' },
mode: { type: 'string', enum: ['a', 'b'], description: 'Operation mode' },
},
handler: async (args, ctx) => {
// Return string for simple text results
return 'result text';
// Or return ToolResult for complex/error content
return { content: [{ type: 'text', text: '...' }], isError: true };
},
});
// Export as Cloudflare Worker — handles MCP, health, assets, and CORS
export default app;You can also use the createApp() factory:
import { createApp } from '@construct-computer/app-sdk';
const app = createApp({ name: 'my-app', version: '1.0.0' });RequestContext
Available in every tool handler as the second argument:
interface RequestContext {
userId?: string; // From x-construct-user header
auth?: { // From x-construct-auth header
type: 'oauth2' | 'api_key' | 'bearer' | 'basic';
access_token?: string; // OAuth2
refresh_token?: string; // OAuth2
expires_at?: number; // OAuth2
[key: string]: unknown; // Dynamic fields from api_key/bearer/basic schemes
};
isAuthenticated: boolean; // Whether valid credentials are present
request: Request; // Raw incoming request
env: Record<string, string>; // App environment variables from x-construct-env
}The auth shape depends on the scheme configured in your manifest.json:
oauth2:access_token,refresh_token,expires_atapi_key/bearer/basic: fields match thenamevalues in yourauth.schemes[].fields[]definition
requireAuth(ctx)
Throws if the user hasn't connected their account. Use in tools that need authentication:
import { requireAuth } from '@construct-computer/app-sdk';
app.tool('my_private_tool', {
description: 'Needs auth',
handler: async (args, ctx) => {
requireAuth(ctx); // throws if not authenticated
const res = await fetch('https://api.example.com', {
headers: { Authorization: `Bearer ${ctx.auth!.access_token}` },
});
return await res.text();
},
});Client-side SDK (construct.*)
The Construct platform injects these globals into every app iframe:
| API | Description |
|---|---|
| construct.ready(callback) | Run code when the SDK bridge is ready |
| construct.tools.call(name, args) | Call a tool, get the full result object |
| construct.tools.callText(name, args) | Call a tool, get just the text result |
| construct.ui.setTitle(title) | Update the window title bar |
| construct.ui.getTheme() | Get the current theme ({ mode, accent }) |
| construct.ui.close() | Close the app window |
| construct.state.get() | Read persistent app state |
| construct.state.set(state) | Write persistent app state |
| construct.state.onUpdate(callback) | Subscribe to state changes (from agent or other tabs) |
| construct.agent.notify(message) | Send a message to the AI agent |
CSS variables (--c-bg, --c-surface, --c-text, --c-accent, etc.) and utility classes are provided by construct.css for theme-aware styling.
Authentication
Configure auth schemes in manifest.json. The SDK supports four types:
OAuth2:
{
"auth": {
"schemes": [{
"type": "oauth2",
"authorization_url": "https://example.com/oauth/authorize",
"token_url": "https://example.com/oauth/token",
"scopes": ["read", "write"]
}]
}
}API Key / Bearer / Basic:
{
"auth": {
"schemes": [{
"type": "api_key",
"label": "Connect your API key",
"instructions": "Get your API key from https://example.com/settings",
"fields": [
{ "name": "api_key", "displayName": "API Key", "type": "password", "required": true }
]
}]
}
}Credential values are delivered to your tool handlers via ctx.auth.
Development
pnpm dev # Start local dev server (wrangler dev)Test your MCP endpoint:
# Health check
curl http://localhost:8787/health
# List tools
curl -X POST http://localhost:8787/mcp \
-H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","method":"tools/list","id":1}'
# Call a tool
curl -X POST http://localhost:8787/mcp \
-H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"greet","arguments":{"name":"World"}}}'Testing in Construct
With the dev server running:
- Open Construct > Settings > Developer
- Toggle Developer Mode on
- Under Connect Dev Server, paste
http://localhost:8787and click Connect
Construct calls your server's /health and /mcp endpoints to register the app, and opens your UI in a sandboxed window.
Publishing
- Push your app to a public GitHub repository
- Fork construct-computer/app-registry
- Add
apps/your-app-id.json:{ "repo": "https://github.com/you/your-app", "versions": [ { "version": "1.0.0", "commit": "<40-char SHA>", "date": "2026-04-16" } ] } - Open a PR — CI validates automatically
- Once merged, your app appears in the Construct App Store
See the full guide at registry.construct.computer/publish.
Links
- Construct Platform
- App Store
- Publishing Guide
- Sample App (Text Tools) — template repo for new apps
