# Fine Structure MCP Server - Agent Integration Guide > This page is designed for AI agents. Read it to learn how to build, update, > and publish web applications via the Fine Structure platform. --- ## 1. Server Base URL: https://finestructure.ai MCP Endpoint: POST /api/mcp Protocol: JSON-RPC 2.0 over HTTP Docs: GET /api/mcp/docs (this page) Manifest: GET /api/mcp/manifest (tool schemas) Health: GET /api/mcp/health Repository guide for developers: backend/mcp/MCP_AGENT_GUIDE.md --- ## 2. Authentication Use an MCP token for server-to-server communication: Authorization: Bearer fse2_mcp_ Create tokens from the Studio MCP Server tab, or via `POST /api/auth/mcp-tokens` with a logged-in user JWT. Fast local setup for agents: ```bash npx @finestructure/agent@latest connect ``` This starts a short browser approval flow. The signed-in owner approves the request and the CLI stores an MCP client config at `~/.finestructure/mcp-config.json`. The CLI receives the raw token once, then the temporary approval request is consumed. Useful variants: ```bash npx @finestructure/agent@latest connect --readonly npx @finestructure/agent@latest connect --full npx @finestructure/agent@latest connect --app app_xxxxx ``` Each token has scopes, rate limits, optional expiry, and can optionally be locked to one `app_id`. Security rules: - An account token can work only with apps owned by the token owner. - `create_app` requires the `create` scope and is available only for account tokens, not app-locked tokens. - An app-locked token can work only with its configured `app_id`. - Agency tools are a separate account-level mode. They require `agency:read` or `agency:write` and are blocked for app-locked tokens. - Agency Mode does not create verified user accounts for clients. It creates a claim link; ownership transfers only after the invited email logs in and accepts the claim. - A client cannot bypass this by sending another `app_id`; the server checks ownership and token app scope before every tool call. - The raw token is shown only once. Store it in the MCP client secret store. Example MCP client config: ```json { "mcpServers": { "fine-structure": { "url": "https://finestructure.ai/api/mcp", "headers": { "Authorization": "Bearer fse2_mcp_" } } } } ``` --- ## 3. Tools - Overview Start every unfamiliar MCP session with: - `get_platform_guide()` to load Fine Structure / FSe2 concepts, safety rules, app file conventions, entity data rules, validation, publishing, A/B testing, SaaS, secrets, integrations, and rollback guidance. - `get_recommended_workflow(task_type, app_id, goal)` to get the correct ordered tool sequence for create, edit, direct file edit, runtime fix, seed data, publish, A/B test, SaaS setup, schema change, secrets/integrations, version restore, or preview inspection work. | Tool | Scope | Async | Description | |--------------------|----------|-------|------------------------------------------| | `create_app` | create | yes | Create a new app from a prompt | | `agency_create_client` | agency:write | no | Create/update a client record without creating a user | | `agency_create_client_app` | agency:write | yes | Generate an agent-owned client app plus claim link | | `agency_create_claim_link` | agency:write | no | Create a claim link for an existing agent-owned app | | `agency_list_clients` | agency:read | no | List agency clients and handoff status | | `agency_get_claim_status` | agency:read | no | Inspect one app handoff status | | `update_app` | update | yes | Update an existing app | | `publish_app` | publish | no | Publish app and configure live access | | `get_job_status` | read | no | Poll async job progress | | `get_app_status` | read | no | App overview: files, URL, active jobs | | `get_app_links` | read | no | Link URLs plus public/private/login status | | `list_app_domains` | read | no | Domain, verification, SSL, redirect status | | `add_custom_domain` | publish | no | Connect a custom domain like Studio | | `get_domain_verification` | read | no | DNS records/instructions for domain setup | | `check_domain_verification` | publish | no | Refresh custom-domain verification status | | `set_primary_domain` | publish | no | Choose primary app host | | `remove_custom_domain` | publish | no | Remove configured custom domain | | `get_domain_ssl_status` | read | no | Inspect SSL/certificate status | | `configure_domain_redirects` | publish | no | Configure primary/www/HTTPS redirects | | `list_apps` | read | no | List all apps owned by user | | `get_app_files` | read | no | File tree with sizes | | `get_app_detail` | read | no | Deep introspection: entities, pages, etc. | | `get_entities` | read | no | Entity schemas + record counts | | `get_pages` | read | no | Page list with file paths | | `get_errors` | read | no | Recent runtime errors | | `list_saved_versions` | versions:read | no | List restorable Saved Versions | | `restore_saved_version` | versions:write | no | Restore a Saved Version with confirm=true | | `create_change_set` | update, files:write | no | Start an atomic file transaction | | `add_file_change` | update, files:write | no | Stage one file upsert/delete in a change set | | `rename_app_file` | update, files:write | no | Rename or move one app file atomically | | `validate_change_set` | read, files:read | no | Validate staged changes before apply | | `apply_change_set` | update, files:write | no | Apply staged files atomically after validation | | `discard_change_set` | update, files:write | no | Discard a pending change set | | `validate_app` | read, files:read | no | Validate syntax, imports, routes, entities, runtime errors | | `inspect_preview` | read, files:read | no | Inspect preview URLs, persisted errors, static click targets | | `get_app_security_context` | security:read | no | Owner, collaborators, app members, policies, and audit | | `set_route_policy` | security:write | no | Store server-owned route auth/role policy | | `set_entity_policy` | security:write | no | Store server-side entity read/write role policy | | `list_app_members` | security:read | no | List generated-app runtime members | | `invite_app_member` | security:write | no | Provision a generated-app runtime member | | `update_app_member_role` | security:write | no | Change a generated-app member role | | `remove_app_member` | security:write | no | Remove a generated-app member | | `get_app_security_audit` | security:read | no | Read recent app security audit events | | `compare_saved_versions` | versions:read | no | Diff two Saved Versions | | `compare_current_to_version` | versions:read, files:read | no | Diff current app files against a Saved Version | | `restore_file_from_version` | versions:write, files:write | no | Restore one file from a Saved Version | | `create_entity_schema` | data:write, files:write | no | Create one entity schema file | | `update_entity_schema` | data:write, files:write | no | Update one entity schema file | | `validate_entity_relationships` | data:read, files:read | no | Check entity relations and schema targets | | `update_entity_metadata` | data:write, files:write | no | Update entity indexes/metadata | | `list_secret_keys` | secrets:read | no | List secret names only, never values | | `set_secret` | secrets:write | no | Store one encrypted app secret | | `delete_secret` | secrets:write | no | Delete one app secret | | `list_integrations` | integrations:read | no | List safe integration configuration | | `configure_integration` | integrations:write | no | Store non-secret integration metadata | | `list_ab_tests` | read | no | List native platform A/B tests | | `create_ab_test` | update | no | Create a native A/B test | | `update_ab_test` | update | no | Update a native A/B test | | `delete_ab_test` | update | no | Delete a native A/B test | | `get_ab_test_stats` | read | no | Get A/B test stats and conversion rates | --- ## 4. Recommended Agent Bootstrap Before making changes: ``` 1. get_platform_guide() 2. list_apps() or get_app_detail(app_id) 3. get_recommended_workflow(task_type, app_id, goal) 4. Follow the returned tool sequence. 5. Validate with validate_app or validate_change_set before finishing. ``` Supported workflow task types: - `create_app` - `edit_app` - `direct_file_edit` - `fix_runtime_error` - `seed_data` - `publish_app` - `domain_setup` - `ab_test` - `saas_setup` - `security_setup` - `agency_client_app` - `schema_change` - `secrets_integration` - `version_restore` - `inspect_preview` Use these tools instead of guessing internal conventions. --- ## 5. Data Model - What the Agent Can See Additional app-scoped workspace tools: - `get_platform_guide(app_id, sections, task_type)` returns platform rules and optional safe app context. It never exposes file contents or secrets. - `get_recommended_workflow(task_type, app_id, goal)` returns the correct tool sequence, validation checklist, and rollback plan. - `read_app_file(app_id, path)` and `read_app_files(app_id, paths)` read app VFS files. - `write_app_file(app_id, path, content)` replaces or creates one app VFS file. - `patch_app_file(app_id, path, old_text, new_text)` applies a targeted exact-text patch. - `rename_app_file(app_id, old_path, new_path, overwrite, force)` renames or moves one app VFS file atomically by staging the new path and deleting the old path in a validated change set. - `query_entity(app_id, entity, filters, sort, limit, offset)` reads app data records. - `seed_entity(app_id, entity, records, mode)` inserts seed records during CREATE or EDIT. - `update_entity_record(app_id, entity, record_id, data)` updates one app data record. - `get_preview_url(app_id)` returns editor, preview, and public URLs. - `get_app_links(app_id)` returns editor, preview, `/p/{app_id}`, subdomain, and custom-domain links, including whether each link is not published, private, public-open, app-login gated, or Studio-login gated. - `list_app_domains(app_id)`, `add_custom_domain(...)`, `get_domain_verification(...)`, `check_domain_verification(...)`, `set_primary_domain(...)`, `remove_custom_domain(...)`, `get_domain_ssl_status(...)`, and `configure_domain_redirects(...)` manage the same app custom-domain capability exposed in Studio. They return exact DNS instructions for the user/admin; they do not create DNS records at the user's DNS provider and do not expose provider-zone administration. - `list_saved_versions(app_id)` lists versions that can be restored. - `restore_saved_version(app_id, snapshot_id, confirm=true)` restores files, records, settings, and A/B state captured in a Saved Version. - `create_change_set(app_id, label)` starts an atomic edit session. - `add_file_change(app_id, change_set_id, path, content, operation)` stages file upserts or deletes. - `validate_change_set(app_id, change_set_id)` validates all staged files before they touch the live app. - `apply_change_set(app_id, change_set_id)` applies the staged files together and creates a Saved Version first. - `discard_change_set(app_id, change_set_id)` abandons staged changes without modifying the app. - `validate_app(app_id)` checks syntax, imports, routes/pages, entity schemas, missing entities, and persisted runtime errors. - `inspect_preview(app_id, route)` returns preview URLs, persisted runtime/network errors, page list, and static clickable candidates from source. It does not fabricate live console logs or screenshots when no browser session is attached. - `get_app_security_context(app_id)` returns safe owner/collaborator/member context, route policies, entity policies, and recent audit events. It never returns secrets. - `set_route_policy(app_id, route, require_auth, allowed_roles, redirect_to)` stores server-owned route policy. `inspect_preview` can simulate access with `auth_as` or `auth_as_role`, and generated apps can call `fse2.security.checkRoute(route)`. - `set_entity_policy(app_id, entity, read, write)` enforces read/write roles on `/api/entities/*` for that entity. No policy means legacy behavior. - `list_app_members`, `invite_app_member`, `update_app_member_role`, and `remove_app_member` manage generated-app runtime users, not Studio collaborators. - `agency_create_client`, `agency_create_client_app`, `agency_create_claim_link`, `agency_list_clients`, and `agency_get_claim_status` are only for account-level Agency Mode. The app stays owned by the agent until the client accepts the claim link while logged in with the invited email. - `compare_saved_versions(...)`, `compare_current_to_version(...)`, and `restore_file_from_version(...)` provide targeted version diffs and one-file recovery. - `create_entity_schema(...)`, `update_entity_schema(...)`, `validate_entity_relationships(...)`, and `update_entity_metadata(...)` manage app schemas. - `list_secret_keys(...)`, `set_secret(...)`, and `delete_secret(...)` manage encrypted secret values. Secret values are never returned by MCP tools. - `list_integrations(...)` and `configure_integration(...)` manage safe, non-secret integration configuration. Put credentials in secrets. - `create_ab_test(app_id, name, page_name, variants, is_active)` creates a native platform A/B test. - `list_ab_tests(app_id)`, `update_ab_test(...)`, `delete_ab_test(...)`, and `get_ab_test_stats(app_id, test_id)` manage and inspect native A/B tests. ### Publishing and link access `publish_app` accepts an `access_mode`: - `public_open`: publish and allow anonymous visitors to view. - `public_auth`: publish and allow the URL to open, but require generated-app login. - `private`: publish a snapshot for owner/collaborator review, but block external visitors. - `keep`: preserve current visibility/auth settings. - `infer`: preserve an existing `auth_required` setting, otherwise infer from app files and app type. Agents should still decide from user intent or ask when ambiguous. Use `get_app_links(app_id)` before sharing URLs. It returns each link with status: `not_published`, `private`, `public_open`, `app_login_required`, `studio_login_required`, or `domain_not_active`. ### Custom domains MCP custom-domain tools are intentionally limited to the same app-level capability exposed in Studio: - `list_app_domains(app_id)` returns platform path, subdomain, custom-domain, verification, SSL, redirect, publish, and login-gating status. - `add_custom_domain(app_id, domain)` connects or replaces the app custom domain and returns DNS records to configure. - `get_domain_verification(app_id, domain)` returns the exact TXT/CNAME records and instructions for the user's DNS admin. - `check_domain_verification(app_id, domain)` refreshes verification and SSL state after the user has configured DNS. - `set_primary_domain(app_id, domain)` and `configure_domain_redirects(app_id, primary_domain, redirect_www, force_https)` manage app-level primary/redirect behavior. - `remove_custom_domain(app_id, domain)` removes the app custom domain. Boundary: MCP must not create or modify DNS-provider records, must not request provider zone IDs, and must not expose direct DNS admin capabilities. It only returns exact DNS instructions and checks the app domain status through Fine Structure's existing custom-domain flow. ### Security policies and generated-app members Use these tools when an app needs real access control instead of only hiding UI elements in React. Route policy example: ```json { "name": "set_route_policy", "arguments": { "app_id": "app_123", "route": "/admin/*", "require_auth": true, "allowed_roles": ["owner", "admin"], "redirect_to": "/Login" } } ``` Entity policy example: ```json { "name": "set_entity_policy", "arguments": { "app_id": "app_123", "entity": "Bookings", "read": ["owner", "admin", "staff"], "write": ["owner", "admin"] } } ``` Important boundaries: - `get_app_security_context` returns safe owner and collaborator context, generated-app runtime members, route policies, entity policies, and audit. It never returns secret values. - App members are generated-app runtime users. They are not Studio collaborators and they are not allowed to change the Fine Structure owner. - MCP member tools cannot assign the `owner` role. - Route policies are stored by the platform. Generated apps can call `fse2.security.checkRoute(route)` or `fse2.security.requireRouteAccess(route, fallback)`. - Entity policies are enforced server-side for configured entities. No policy means legacy behavior for that entity. - `inspect_preview` accepts `auth_as` and `auth_as_role` to simulate route policy checks for anonymous users and roles. - Security tools require `security:read` or `security:write` and still require app ownership or the correct app-locked token. When you call `get_app_detail`, you receive a full snapshot of the app: ``` { "app_id": "...", "app_name": "My CRM", "url": "https://my-crm.finestructure.ai", "entities": [ // Data models (like DB tables) { "name": "customers", "schema": { // JSON Schema with field types "fields": [ {"name": "first_name", "type": "string"}, {"name": "email", "type": "string"}, {"name": "company", "type": "relation", "target": "companies"} ] }, "record_count": 42 } ], "pages": ["pages/Dashboard.jsx", "pages/Customers.jsx", ...], "components": ["components/CustomerCard.jsx", ...], "cron_jobs": [...], // Scheduled tasks "has_layout": true, // Whether Layout.jsx exists "settings": {...}, // App configuration "integrations": [ // Connected services {"provider": "stripe", "is_active": true} ], "env_var_names": ["STRIPE_KEY", "SMTP_HOST"], // Names only, never values "total_files": 15 } ``` --- ## 5. Flow: Create a New App ``` Agent Fine Structure | | | tools/call: create_app(name, prompt) | |--------------------------------------------->| | {app_id, job_id, status: "generating"} | |<---------------------------------------------| | | | [POLL] tools/call: get_job_status(job_id) | |--------------------------------------------->| | {status: "running", event_count: 5} | |<---------------------------------------------| | | | [POLL again after 3-5s] | |--------------------------------------------->| | {status: "done", result_summary: {...}} | |<---------------------------------------------| | | | tools/call: get_app_detail(app_id) | |--------------------------------------------->| | {entities: [...], pages: [...], ...} | |<---------------------------------------------| | | | tools/call: publish_app(app_id, access_mode)| |--------------------------------------------->| | {url: "https://xxx.finestructure.ai"} | |<---------------------------------------------| ``` ### Recommended polling interval: 3-5 seconds ### Typical generation time: 15-60 seconds --- ## 6. Flow: Edit an Existing App ``` 1. list_apps() -> Find the app to edit 2. get_app_detail(app_id) -> Understand current state 3. get_entities(app_id) -> See data models 4. get_errors(app_id) -> See any bugs 5. update_app(app_id, prompt) -> Send changes 6. get_job_status(job_id) -> Poll until done 7. get_app_detail(app_id) -> Verify changes 8. get_app_links(app_id) -> Confirm current link status 9. publish_app(app_id, access_mode) -> Go live 10. get_app_links(app_id) -> Verify the shared URL works ``` --- ### Direct File-Edit Flow Use `update_app(app_id, prompt)` when you want Fine Structure's generator to make the change. Use change sets when the external agent is editing files directly: ``` 1. get_app_detail(app_id) 2. list_saved_versions(app_id) 3. validate_app(app_id) 4. create_change_set(app_id, label) 5. add_file_change(app_id, change_set_id, path, content, operation="upsert") 6. validate_change_set(app_id, change_set_id) 7. apply_change_set(app_id, change_set_id) 8. inspect_preview(app_id) 9. get_app_links(app_id) 10. publish_app(app_id, access_mode) 11. get_app_links(app_id) ``` For a one-file rename or move, use `rename_app_file(app_id, old_path, new_path)` instead of manually combining write/delete calls. It creates a Saved Version, validates the resulting file map, and applies the rename atomically. `apply_change_set` creates a Saved Version before writing. If validation fails, it blocks by default and returns focused issues. Pass `force=true` only when a human explicitly approves applying with known errors. --- ## 7. Example: Full Agent Loop ```python import requests, time BASE = "https://finestructure.ai/api/mcp" TOKEN = "fse2_mcp_..." HEADERS = {"Authorization": f"Bearer {TOKEN}", "Content-Type": "application/json"} def mcp_call(method, params=None): r = requests.post(BASE, json={ "jsonrpc": "2.0", "id": int(time.time()), "method": method, "params": params or {} }, headers=HEADERS) data = r.json() if "error" in data: raise Exception(data["error"]["message"]) return data["result"] # 1. Create result = mcp_call("tools/call", { "name": "create_app", "arguments": { "name": "Employee Dashboard", "prompt": "Build an employee management dashboard with HR features" } }) import json info = json.loads(result["content"][0]["text"]) job_id = info["job_id"] app_id = info["app_id"] # 2. Poll until done while True: status = mcp_call("tools/call", { "name": "get_job_status", "arguments": {"job_id": job_id} }) status_data = json.loads(status["content"][0]["text"]) if status_data["is_done"]: break time.sleep(5) # 3. Inspect detail = mcp_call("tools/call", { "name": "get_app_detail", "arguments": {"app_id": app_id} }) # 4. Publish pub = mcp_call("tools/call", { "name": "publish_app", "arguments": {"app_id": app_id, "access_mode": "public_open"} }) pub_data = json.loads(pub["content"][0]["text"]) print(f"Live at: {pub_data['url']}") print(pub_data["links"]["publication"]) ``` --- ## 8. JSON-RPC 2.0 Format ### Request ```json { "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "", "arguments": { ... } } } ``` ### Success response ```json { "jsonrpc": "2.0", "id": 1, "result": { "content": [{ "type": "text", "text": "{\"key\": \"value\"}" }] } } ``` ### Error response ```json { "jsonrpc": "2.0", "id": 1, "error": { "code": -32001, "message": "Human-readable error" } } ``` ### Supported methods - `initialize` - Protocol handshake (optional) - `tools/list` - List available tools - `tools/call` - Execute a tool --- ## 9. Limits | Limit | Default | Notes | |---------------------|------------|------------------------------------| | Requests per minute | 30 | Per token, configurable | | Requests per day | 500 | Per token, configurable | | Prompt max length | 10,000 ch | Truncated silently | | App name max | 100 chars | Truncated silently | | Max errors returned | 50 | Via `limit` parameter | Credit accounting is enforced server-side and is not exposed through MCP docs or tool responses. --- ## 10. Best Practices 1. **Be specific in prompts** - describe pages, features, data models, and styling. Bad: "Build a CRM." Good: "Build a CRM with customer list, deal pipeline (kanban), task manager, and a dashboard with revenue charts." 2. **Always poll jobs** - `create_app` and `update_app` are async. Never assume instant completion. Use `get_job_status` in a loop with 3-5s intervals. 3. **Inspect before editing** - call `get_app_detail` + `get_entities` to understand the current state before sending an `update_app`. 4. **Iterate incrementally** - send focused prompts. "Add a settings page" is better than rewriting the entire app description. 5. **Check errors before updating** - if `get_errors` returns issues, address them in your update prompt. 6. **Use change sets for direct file edits** - stage related file changes, call `validate_change_set`, then `apply_change_set`. Do not write a multi-file feature one file at a time unless a human asks for that. 7. **Publish explicitly** - apps are not live until you call `publish_app`. Before publishing, choose `access_mode`: `public_open` for public landing/marketing sites, `public_auth` for dashboards/CRMs/SaaS/member areas, `private` for owner-only snapshots, or `infer` when the app should follow existing settings and obvious app structure. Ask the user when the intended audience is unclear. 8. **Verify links before sharing** - call `get_app_links` after publishing and return only URLs whose status matches the intended audience. 9. **Keep domain setup within Studio scope** - use `add_custom_domain` and `get_domain_verification` to return DNS records for the user/admin to add. Do not ask for DNS-provider zone IDs or try to modify provider DNS records. 10. **Handle rate limits** - if you get error `-32002`, inspect `retry_after` in `error.data` and wait before retrying. 11. **Store job_ids and change_set_ids** - you'll need them for polling or applying. Don't discard the response. --- ## 11. Common Failures & How to Handle Them | Error Code | Meaning | Resolution | |------------|------------------------|-----------------------------------------| | -32001 | Auth failed | Check token is valid, not expired | | -32002 | Rate limited | Wait `error.data.retry_after` seconds | | -32003 | Credits exhausted | User must upgrade plan or wait for reset | | -32004 | Permission denied | Token missing required scope, or not app owner | | -32602 | Invalid params | Check tool argument names and types | | -32601 | Unknown method | Use `initialize`, `tools/list`, or `tools/call` | | ValueError | "App not found" | Verify app_id exists and you own it | | ValueError | "Prompt too short" | Send at least 10 characters for create, 5 for update | ### When a job fails If `get_job_status` returns `result_summary.error`, the generation failed. Common causes: - AI provider timeout - retry the same prompt - Prompt too vague - add more detail - Credit limit hit mid-generation - check credits --- ## 12. Scopes Reference Tokens have granular scopes: | Scope | Allows | |-----------|------------------------------------------| | `create` | `create_app` | | `update` | `update_app` | | `publish` | `publish_app` and app custom-domain setup | | `read` | General get_* and list_* tools | | `generate:edit` | AI edit generation | | `files:read` | Read app source files | | `files:write` | Write or patch app source files | | `data:read` | Query app data records | | `data:write` | Seed and update app data records | | `ab:read` | A/B test reads and stats | | `ab:write` | A/B test create, update, and delete | | `versions:read` | List Saved Versions | | `versions:write` | Restore Saved Versions | | `secrets:read` | List secret key names only | | `secrets:write` | Create/update/delete encrypted secrets | | `integrations:read` | List safe integration metadata | | `integrations:write` | Configure non-secret integration metadata | | `security:read` | Read app security context, members, policies, audit | | `security:write` | Manage route policies, entity policies, and app members | | `agency:read` | Read agency client and claim status | | `agency:write` | Create client records, client apps, and claim links | `restore_saved_version` requires `versions:write` and `confirm=true`. You can restrict tokens to read-only for monitoring. --- ## 13. Quick start (self-serve, free tier) Onboarding is fully self-serve; no sales call and no credit card are needed. 1. A human signs up free at https://finestructure.ai. The free tier includes daily AI generations, so real apps can be built and tested before paying. 2. Create a scoped MCP/API token at https://finestructure.ai/api-keys (self-serve; scoped by account, app, and permissions). 3. Connect over MCP (`https://finestructure.ai/api/mcp`, Streamable HTTP) or REST, passing `Authorization: Bearer fse2_mcp_`. 4. Call `get_platform_guide`, then `get_recommended_workflow`. 5. A sandbox environment is available at https://finestructure.ai/sandbox for safe experiments. CLI for MCP clients: `npx @finestructure/agent` (https://www.npmjs.com/package/@finestructure/agent). ## 14. When to use Fine Structure Use Fine Structure when the task involves: building a complete web app, SaaS product, dashboard, CRM, portal, or landing page from a description; editing, validating, versioning, or publishing an existing Fine Structure app; managing app data (entity schemas, seeds, queries, records); configuring domains, secrets, integrations, A/B tests, or email marketing for a generated app; or operating AI agent teams and a business knowledge base for an account. Do NOT use it for generic web search or for anything outside the connected token's granted scope. ## 15. REST bridge and OpenAPI Every MCP tool is also exposed as a plain REST operation: - `POST /api/mcp/tools/{tool_name}` with the tool's arguments object as the JSON body and the same Bearer token. Errors use standard HTTP status codes (400/401/402/403/404/429/500) with a JSON `{"error": {code, message}}` body. - The full OpenAPI 3.1 specification (one operation per tool, with exact JSON schemas) is published at `https://finestructure.ai/openapi.json` and at `GET /api/mcp/openapi.json`. It is compatible with OpenAI, Claude, and Gemini function calling. - API catalog (RFC 9727): `https://finestructure.ai/.well-known/api-catalog`. ## 16. Webhooks and signature verification Fine Structure pushes agent lifecycle events (`conversation.created`, `message.created`, `message.completed`, plus `ping` for tests) to owner-defined URLs. Subscriptions are managed at https://finestructure.ai/agent-webhooks. Every delivery is signed. Headers on each POST: - `X-FSE2-Signature`: `sha256=`, keyed with the subscription secret. Verify with a constant-time comparison over the exact raw body bytes BEFORE parsing the JSON. - `X-FSE2-Event`: event type. `X-FSE2-Event-Id`: stable id for deduplication. - `X-FSE2-Delivery`: unique delivery attempt id. `X-FSE2-Timestamp`: send time. Python verification example: import hashlib, hmac def verify(raw_body: bytes, header: str, secret: str) -> bool: expected = "sha256=" + hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest() return hmac.compare_digest(expected, header or "") Failed deliveries retry with exponential backoff (up to 6 attempts) and then dead-letter. Respond 2xx quickly to acknowledge. Full guide: https://finestructure.ai/docs/webhooks ## 17. Support - Health check: `GET /api/mcp/health` - Tool schemas: `GET /api/mcp/manifest` - OpenAPI spec: `GET /api/mcp/openapi.json` - This guide: `GET /api/mcp/docs` - Human support: support@finestructure.ai ยท https://finestructure.ai/contact