chatgpt-app:validate-2cfba9 — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited chatgpt-app:validate-2cfba9 (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
You are helping the user validate their ChatGPT App before testing and deployment.
Check that ALL mandatory files exist:
ls package.json tsconfig.server.json setup.sh START.sh .env.example server/index.tsExpected file structure:
{app-name}/
├── package.json
├── tsconfig.server.json
├── setup.sh
├── START.sh
├── .env.example
├── .gitignore
└── server/
└── index.tsIf ANY of the above are missing, report as CRITICAL ERROR.
Verify server/index.ts uses correct patterns:
MUST have:
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";MUST NOT have:
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; // WRONGSession management MUST exist:
const transports = new Map<string, StreamableHTTPServerTransport>();Verify widgets array structure:
const widgets: WidgetConfig[] = [
{
id: "widget-id", // kebab-case
name: "Widget Name",
description: "What it displays",
templateUri: "ui://widget/widget-id.html", // MUST match this format
invoking: "Loading...",
invoked: "Ready",
mockData: { /* sample data */ },
},
];Widget tools MUST return:
return {
content: [{ type: "text", text: JSON.stringify(result) }],
structuredContent: result, // CRITICAL - becomes window.openai.toolOutput
_meta: {
"openai/outputTemplate": widget.templateUri,
"openai/toolInvocation/invoked": widget.invoked,
},
};Verify ReadResource returns correct format:
return {
contents: [{
uri,
mimeType: "text/html+skybridge", // MUST be this exact value
text: generateWidgetHtml(widget.id),
}],
_meta: {
"openai/serialization": "markdown-encoded-html",
"openai/csp": { ... },
},
};Verify generateWidgetHtml includes:
window.PREVIEW_DATA support for local previewwindow.openai.toolOutput data accessopenai:set_globals event listenerrendered flag to prevent re-rendersVerify these endpoints exist:
GET /health - Health checkGET /preview - Widget preview indexGET /preview/:widgetId - Individual widget previewALL /mcp - MCP protocol handlerDELETE /mcp - Session cleanup{
"scripts": {
"build": "npm run build:server",
"build:server": "tsc -p tsconfig.server.json",
"start": "HTTP_MODE=true node dist/server/index.js",
"dev": "HTTP_MODE=true NODE_ENV=development tsx watch --clear-screen=false server/index.ts"
}
}MUST NOT have: dev:web, build:web, concurrently
readOnlyHint: truedestructiveHint: trueopenWorldHint: trueuser_subject column## Validation Results
### File Structure ✓
All required files present.
### Server Implementation ✓
Using correct Server class with session management.
### Widget Configuration ✓
2 widgets properly configured.
### Tool Responses ✓
All widget tools return structuredContent.
---
**Overall: PASS**| Error | Fix |
|---|---|
| Uses McpServer | Change to Server from @modelcontextprotocol/sdk/server/index.js |
| Missing session management | Add Map<string, StreamableHTTPServerTransport> |
| Wrong widget URI | Use ui://widget/{id}.html format |
| Wrong MIME type | Use text/html+skybridge |
| Missing structuredContent | Add to tool response for widget data |
| Has web/ folder | Remove - widgets are inline in server/index.ts |
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.