MCP Server Production Readiness
Validation checklist for SteelEngine's MCP server — what to run before shipping changes that touch the LLM-facing surface.
SteelEngine exposes its own MCP server at /api/mcp/steelengine (consumed by Claude Desktop, Cursor, and any external MCP client). This page is the checklist for verifying that surface is production-ready after changes. The legacy /api/mcp/copilot route remains available as a compatibility alias.
This is the server-side readiness check. For configuring MCP clients (external tools you connect to SteelEngine), see Using MCP Tools.
What the MCP server can expose
tools/list is principal-specific. The table below is the complete server catalog, not a promise that every authenticated client receives every tool or operation. Discovery can prune operation enums and oneOf branches, and omits a tool when no operation remains visible.
| Tool | Resource | Operations |
|---|---|---|
steelengine_workspaces | Workspaces | 9 ops including list, get, create, rename, update, member management |
steelengine_workflows | Workflows + workflow folders | 47 ops including block editing, run, deploy, version, clone/export/import, folder CRUD |
steelengine_docs | Workspace docs + doc folders | 17 ops including read, write, patch, glob, search, folder CRUD |
steelengine_tables | Workspace tables + databases | 25 ops including row CRUD, schema, column management, import, database grouping |
steelengine_apps | Apps (dashboard builder) | 15 ops including app/page CRUD, widget editing, validate, publish, rollback, versions |
steelengine_knowledge | Knowledge bases | 18 ops including KB CRUD, documents, tags, connectors |
steelengine_jobs | Scheduled jobs | 9 ops including create, pause, resume, logs |
steelengine_env | Environment variables + BYOK keys | 7 ops including list, get, set, delete, BYOK key management |
steelengine_credentials | OAuth credentials + API keys | list, auth_link, rename, delete, generate_api_key |
steelengine_mcp_servers | Workflow + external MCP servers | 8 ops |
steelengine_skills | Workspace skills | list, add, edit, delete |
steelengine_memory | Workspace memory | list, search, add, correct, delete |
steelengine_logs | Execution / workflow / job logs | execution_summary, workflow_logs, job_logs, audit |
steelengine_platform | Platform discovery (blocks, triggers, docs search, VFS) | 9 read-only ops |
steelengine_a2a | A2A agents | 7 ops including list, get, create, update, publish/unpublish |
The server advertises domain tools only. Natural-language subagent tools are not part of the public MCP contract; external clients should call the appropriate steelengine_* domain tool directly.
Pre-ship checklist
1. Type check passes
cd apps/web && bun run type-checkMust exit cleanly. TypeScript catches the most common breakage class (renamed types, missing imports, schema/handler drift). The MCP server code lives entirely under apps/web (lib/copilot/tools/mcp/, app/api/mcp/), so this one check covers the whole surface.
2. PBAC contract tests pass
Run the exact-authorization and discovery tests first:
cd apps/web && bunx vitest run \
lib/mcp/copilot/pbac.test.ts \
lib/mcp/copilot/tool-discovery.test.ts \
app/api/mcp/copilot/route.test.tsThese tests must prove:
- Catalog permission metadata resolves to the correct object and action.
tools/listprunes both the operation enum and discriminatedoneOfbranches.- A tool with zero visible operations is omitted.
tools/callre-authorizes a hidden or stale operation.- API-key scope is applied after owner PBAC authorization as a second cap.
- A denial includes the catalog permission key and target when exact mapping is available.
3. Unit + integration tests pass
# bun:test files (MCP definitions, tool-id-aliases)
cd apps/web && bun test ./lib/copilot/tools/mcp/definitions.test.ts \
./lib/copilot/tools/tool-id-aliases.test.ts
# vitest files (routes, hooks, services)
cd apps/web && bunx vitest run \
lib/copilot/ \
lib/folders/ \
app/api/mcp/ \
app/api/v1/ \
app/api/workspaces/ \
app/api/files/ \
app/api/chat/ \
hooks/queries/Expected: ~1,300 tests pass.
4. End-to-end certification harness against a live deployment
mcp-certify.ts is the production gate. It calls every tool, every operation, against a real workspace and validates response envelopes, error codes, confirmation gates, and structured output contracts.
cd apps/web
# Required environment:
export STEELENGINE_BASE_URL="https://www.steelengine.com" # or staging
export STEELENGINE_MCP_API_KEY="sk-steelengine-..." # workspace API key
export STEELENGINE_ORGANIZATION_ID="org_..." # required for workspace-create probes
# Optional:
export STEELENGINE_WORKSPACE_ID="wsp_..." # reuse existing workspace
export STEELENGINE_OAUTH_BEARER_TOKEN="..." # if testing bearer auth path
bun run mcp:certifyTo run the restricted-role gate, set STEELENGINE_MCP_PBAC_FIXTURES to a JSON array of pre-provisioned API-key fixtures. The harness requires scenarios named custom_role, group_assignment, explicit_deny, role_inheritance, organization_scope, workspace_scope, and api_key_scope. Each fixture can assert visible operations, hidden operations, and direct calls that must return permission_denied:
[
{
"name": "document reader with explicit table deny",
"scenario": "explicit_deny",
"apiKey": "sk-steelengine-...",
"visible": [{ "tool": "steelengine_docs", "operation": "read" }],
"hidden": [{ "tool": "steelengine_tables", "operation": "query_rows" }],
"deniedCalls": [
{
"tool": "steelengine_tables",
"args": { "operation": "query_rows", "tableId": "tbl_..." }
}
]
}
]Provide at least one fixture for every required scenario in the complete array. This makes the live gate verify the actual persisted role, group, inheritance, deny, assignment-scope, and API-key configuration instead of only simulating those decisions in unit tests.
The harness exercises (in order):
- Auth: unauthenticated requests rejected; invalid API key rejected; optional bearer-token auth
tools/listpublic contract- Workspace CRUD via
steelengine_workspaces - All domain operation envelopes
- Workflows + workflow folders
- Docs + doc folders (including
create_folder,rename_folder,move_folder,delete_foldercascade) - Tables
- Knowledge bases
- Jobs
- Environment + credentials
- MCP servers (workflow + external)
- Skills + memory
- Logs + platform discovery
- Credential OAuth link generation through
steelengine_credentials.auth_link - Response envelope + redaction invariants
- Restricted custom-role fixtures: read-only discovery, create-without-update, group-derived access, explicit deny, workspace scope, and API-key scope intersection
Harness runs authenticate with STEELENGINE_MCP_API_KEY (the legacy copilot key variable is no longer read by the scripts).
5. Smoke test (lighter weight)
cd apps/web && bun run mcp:smokeThe smoke harness runs a single happy-path call against each tool. Useful for fast verification after small changes; not a replacement for the cert harness.
Production-readiness criteria
The MCP server is production-ready when:
- Every domain tool has a rich description (when-to-use, when-not, ops, errors, examples, side effects) - see
apps/web/lib/copilot/tools/mcp/domain-tools.ts - Every domain tool has per-op discriminated JSON Schema (
oneOfbranches keyed onoperation) - seeapps/web/lib/copilot/tools/mcp/per-op-schemas.ts - Every advertised operation declares one or more Permission Catalog requirements; no domain relies on the compatibility read/write/admin capability projection
- Every catalog requirement resolves its target only after the execution plan has resolved workspace and resource ownership
-
tools/listfilters operations from owner PBAC plus API-key scope and omits empty tools -
tools/callperforms the same exact check again and does not trust discovery - Custom-role, group, org-wide, workspace-scoped, inheritance, and explicit-deny cases are certified
- Every destructive operation requires
confirm: trueand returnsconfirmation_requiredwhen missing - Every response is a structured envelope:
{success, message?, data?, error?} -
steelengine_credentials.auth_linkreturns structured OAuth URL data - No legacy
steelengine_filesreferences in tool descriptions or prompts - No legacy
'workspace_file'etc. canonical tool IDs (alias map handles historical chat replay) - Folder ops live on the parent resource tool (
steelengine_docs.{,*}_folder,steelengine_workflows.{,*}_folder) - Public REST
/api/v1/docsand/api/v1/docs/foldersmatch MCP semantics - OpenAPI spec documents
folderIdon all relevant ops +DocMetadata.folderId - Audit log writes on every folder mutation (create, update, delete) regardless of surface
All of these must be enforced by the unit tests, the cert harness, or both. Exact PBAC metadata and call enforcement cover the complete domain and direct-tool catalog; changes must keep the zero-legacy-metadata grep gates and restricted-role certification green.
When something fails
- Type check fails → fix the type error. Don't ship.
- Unit test fails (new failure) → fix. Pre-existing failures on
mainare tracked but don't block. - Cert harness fails on a domain tool → that tool has a regression. The harness output tells you which operation. Check the dispatch in
apps/web/app/api/mcp/copilot/route.tsfor that tool's case;/api/mcp/steelenginecurrently re-exports that implementation. - Cert harness fails on response envelope (
returned success=true without message or data) → the response builder for that operation forgot to setmessageordata. Common when extending a tool. - Cert harness fails on credential OAuth link shape → the credentials domain plan or OAuth URL extraction changed. Check
apps/web/lib/mcp/copilot/domain-plans/credentials.tsandapps/web/lib/copilot/orchestrator/tool-executor/oauth-links.ts.
Building production-ready workflows + agents via MCP
The tool surface is intentionally designed so an LLM client can construct a full SteelEngine application without falling through to natural-language subagents:
- Discover the workspace →
steelengine_platform.blocksto learn block types,steelengine_platform.triggersfor trigger types,steelengine_platform.tool_searchto discover integration tools by regex. - Set up infrastructure →
steelengine_credentials.auth_linkto OAuth-connect external services;steelengine_env.setfor API keys;steelengine_knowledge.createfor KBs. - Build workflows →
steelengine_workflows.create_folderto organize,steelengine_workflows.createto make a workflow, thensteelengine_workflows.edit_blocksfor canvas edits. - Iterate →
steelengine_workflows.run/run_until_block/run_from_blockfor partial execution;steelengine_workflows.get_logsfor execution logs. - Deploy →
steelengine_workflows.deploy_api,deploy_chat, ordeploy_mcp(all destructive + confirm-gated). - Manage runtime artifacts →
steelengine_docs.{write,read,patch}for prompts/configs/outputs;steelengine_tables.{insert_row,query_rows}for structured data;steelengine_jobs.createfor scheduled runs.
The MCP surface is the canonical way to programmatically build on SteelEngine. Every operation here has a matching dashboard control, but the dashboard is for humans; MCP is for agents and external code.
Capabilities the server advertises
The MCP server advertises only the tools primitive in its initialize response:
{ "capabilities": { "tools": {} } }resources and prompts are not advertised today. This is intentional:
- Tools are the right primitive for SteelEngine's API surface — every operation is an action the LLM can take.
- Resources would let MCP clients reference docs/tables as MCP-native URIs. Not currently needed because every doc and table is already addressable via
steelengine_docsandsteelengine_tablestools. Addingresourceswould create two ways to do the same thing. - Prompts would expose canned prompt templates. Not currently needed because the LLM-facing prompt-building happens client-side.
If a future use case requires either, both can be added without breaking existing tools/* callers.
Idempotency and replay
The MCP server does not implement idempotency keys. Duplicate tools/call requests execute twice. This is by design: every mutating operation is either:
- Naturally idempotent (e.g.
renameto the same name,setenv var to the same value) - Auto-deduplicated by the underlying handler (e.g.
create_folderauto-suffixes on name collision) - Confirm-gated and destructive (e.g.
delete,deploy_*,revert_to_version) — the LLM must explicitly opt in withconfirm: true, making accidental double-execution unlikely
If your client retries on transient failures, prefer exponential backoff over immediate retry, and check the previous call's outcome via the equivalent list/get operation before retrying a destructive write.
Error code reference
The MCP server emits these structured error codes via error.code on failed envelopes:
| Code | When |
|---|---|
invalid_params | Required field missing or fails JSON Schema validation |
permission_denied | The caller lacks the required catalog permission, an explicit deny applies, or the API-key scope cap excludes the target |
not_found | Targeted resource doesn't exist or is soft-deleted |
workspace_mismatch | Resource exists but belongs to a different workspace |
confirmation_required | Destructive op called without confirm: true |
duplicate_name | Auto-suffix retry cap exhausted on a folder/resource name |
cycle_detected | Folder move would create a parent-child cycle |
ambiguous_table_name | tableName resolves to multiple tables in the workspace |
unsupported_operation | Operation name not recognized for this tool |
tool_failed | Underlying server tool raised; original error in error.details.output |
internal_error | Unexpected error; check logs |
Tool descriptions reference these by name in their ERRORS: section. Errors from the underlying tool layer (e.g. embedding failures, OAuth misconfiguration, storage quota) are surfaced as tool_failed with the original message in error.details.output rather than getting their own top-level codes — keeps the public contract small.