Transport-agnostic MCP surface for Bevel platform: UTCP manual registration, tool discovery, MCP listing/dispatch and code-mode meta-tools. Shared by hosted proxy and local stdio server.
Hexis MCP Core demonstrates strong definition quality with well-structured tools, comprehensive descriptions, and explicit input schemas. All 7 tools have clear names following verb_noun conventions (call_tool_chain, list_tools, tools_info, my_plugin, create_plugin, list_skills, get_skill). Descriptions are generally detailed (ranging from ~90 chars for list_tools to 1500+ chars for call_tool_chain), providing context for when and how to use each tool. All tools have fully typed JSON Schema input definitions with parameter descriptions. The tools show good composition patterns where call_tool_chain wraps access to registered tools (pattern:tool-chain), and discovery tools (list_tools, list_skills) guide agent navigation. Output schemas are partially documented but could be more explicit. Error handling descriptions exist but lack actionable recovery guidance in most cases.
Execute JavaScript code with direct access to all registered UTCP tools as hierarchical functions (e.g. `manual.tool(args)`, synchronous, no await). The runtime is plain JavaScript — no type annotations or other TypeScript-only syntax. Return the final value with `return`. Use `list_tools` and `tools_info` first to discover available tools and their interfaces. Error handling inside the chain: a failing tool call THROWS, and the thrown error's `.message` holds the server's actual reason (e.g. a 403 with the explanation, not just a status code). If you catch it, surface `err.message` (and `err.status` / `err.data` when present) — NEVER `return { error: err }` or otherwise return the raw Error object, because an Error serializes to `{}` (its `message` is non-enumerable) and the reason is lost. If you don't need to handle it, just let it throw — the runtime already reports `err.message` back to you. Large return values: if the returned value exceeds `max_output_size`, the full JSON is auto-spilled to a shared spill store (outside any workspace, never committed) and the response contains only a `__tool_chain_spill__/…` ref + a truncated marker. You can read the spill back with the regular `read_file` tool — pass that ref as `path` (its `branch` is ignored) plus `offset` / `limit` to slice it, never read a multi-MB file in full. Order of preference: (1) re-run `call_tool_chain` with a follow-up code chain that filters/maps the data inline and returns just what you need; (2) narrow the API call — shorter `fields`, tighter date window, lower `limit`; (3) last resort — `read_file` against the spill ref with `offset` / `limit`. The spill is read-only context only; do NOT use it as a way to persist KB content — for KB writes use the regular `write_file` / `edit_file` tools, which go through the lock/commit pipeline.
Create a shared plugin under the plugins root, exactly as the app's New plugin button does: the caller runs it (read, write and owner), and it is discoverable by everyone so people can ask to join. Pass `parent` to make it inside an existing grouping folder under the plugins root (e.g. `Teams`); a plugin cannot be made inside another plugin. Returns the folder and where its skills go. A refusal carries `error` in words. 4xx means the input will not do: 409 when a plugin of that name (or its identifier) exists, 422 for a name the knowledge base cannot carry or a parent that may not hold a plugin, 404 when the parent folder is not there. 503 means the plugin list could not be read completely just now — nothing is wrong with the input; try again shortly.
Output schemas not documented in tool definitions. While call_tool_chain and other tools describe what they return in prose, there is no formal JSON Schema for response objects. LLMs cannot plan subsequent tool invocations without knowing field names and types of returned data.
Error handling descriptions lack actionable recovery guidance. For example, create_plugin mentions '4xx means the input will not do' and '503 means the plugin list could not be read' but does not guide the LLM on what to do next (e.g., 'retry after 5s', 'try with a different name', 'call list_plugins first'). This violates pattern:recovery-guide.
list_tools description is minimal (50 chars): 'Returns a list of all UTCP tool names currently registered, in their TypeScript-accessible form (e.g. `manual.tool`).'. This is below the rubric baseline of 194 chars average and omits WHEN to call this tool (before tools_info? at session start?). Should explain it is a discovery tool for planning.
| Scored | Grade | Overall | Spec posture | Rubric |
|---|---|---|---|---|
| 2026-09-22 | B | 75 | 2026-07-28+ | v2 |
Load a skill by name: returns its full instructions (SKILL.md body) to follow, plus the skill folder path and the list of bundled files. Pass `file` to fetch a bundled file's content (e.g. a script) instead of the body. Loads the latest copy unless `version` names an earlier one the skill declared.
List the available skills (reusable specialist instructions) with their names, descriptions and, for a skill that declares one, its current `version` (its SKILL.md `metadata.version`, else a top-level `version`, else `lifecycle.version`). Discover what skills exist before specialist work, then `get_skill` to load one.
Returns a list of all UTCP tool names currently registered, in their TypeScript-accessible form (e.g. `manual.tool`).
The caller's own private plugin — their personal space in the knowledge base, created on first use. Returns its folder and where skills go inside it (`skillsDir`); write a skill there as `<skillsDir>/<skill-name>/SKILL.md` with the file tools, opening with the Agent Skills frontmatter (`name`, `description`, and `metadata.version` such as `"1.0.0"`). Readable only by its owner — not even admins — and never listed as a shared plugin. Idempotent: calling it again returns the same folder.
Get complete information about a specified list of tools, including TypeScript interface definitions. Accepts either UTCP names or sanitized TS-accessible names (from `list_tools`).
tools_info description lacks guidance on parameter format. It states 'Accepts either UTCP names or sanitized TS-accessible names (from `list_tools`)' but does not provide examples or clarify the difference. LLMs may pass wrong format.
call_tool_chain returns a complex spill-to-file mechanism (__tool_chain_spill__/... refs) but the description of this behavior is embedded in the tool description itself (1600+ chars). This should be extracted to the output schema documentation as a formal response structure so the LLM understands the response format before calling.
No parameter constraints on numeric inputs. call_tool_chain timeout ranges 1000 - 120000 ms (documented), but max_output_size ranges 1000 - 1000000 chars (documented), good. However, list_skills and get_skill lack any documented limits on results or version strings, allowing unbounded or invalid inputs.