| name | myco:mcp-tool-development-lifecycle |
| description | Comprehensive lifecycle for authoring, registering, documenting, and maintaining MCP tools in packages/myco/src/tools/ — covering schema definition in TOOL_DEFINITIONS arrays, handler implementation with DaemonClient patterns, shared tool-runtime registration, documentation bundling, anti-drift testing patterns, and cloud vs local placement decisions. Essential for maintaining the schema ↔ handler ↔ documentation triad that agents depend on for correct tool invocations, even when the user doesn't explicitly ask for MCP tool development.
|
| managed_by | myco |
| user-invocable | true |
| allowed-tools | Read, Edit, Write, Bash, Grep, Glob |
MCP Tool Development and Anti-Drift Maintenance
MCP tools are the primary interface between agents and the Myco intelligence pipeline. Each tool requires a coordinated schema ↔ handler ↔ documentation triad that can drift over time, causing silent agent failures. This skill covers the complete development lifecycle and maintenance procedures to prevent drift regressions.
Prerequisites
- Working Myco development environment with
packages/myco/src/tools/ structure
- Understanding of JSON Schema for parameter definitions
- Familiarity with TypeScript handler patterns and DaemonClient usage
- Knowledge of local vs cloud MCP bifurcation model
- Understanding of shared tool-runtime supporting multiple transports (MCP stdio, HTTP MCP, CLI)
Procedure A: Schema Definition
Define the tool interface in packages/myco/src/tools/definitions.ts (shared tool-runtime definitions):
-
Add tool name constant at the top of the file:
export const TOOL_MY_NEW_TOOL = 'myco_my_new_tool';
-
Add schema entry to the appropriate array (TOOL_DEFINITIONS for local tools, COLLECTIVE_TOOL_DEFINITIONS for Collective-dependent tools):
{
name: TOOL_MY_NEW_TOOL,
description: 'Brief description of what this tool does — agents use this for selection decisions',
cortex: {
guidance: 'Clear guidance for when to use this tool vs alternatives',
priority: 50,
requiresTeam: false,
requiresCollective: false,
},
annotations: {
readOnlyHint: true,
destructiveHint: false,
idempotentHint: true,
openWorldHint: false,
},
inputSchema: {
type: 'object' as const,
properties: {
: {
: ,
:
},
: {
: ,
:
}
},
: []
}
}
Procedure B: Handler Implementation
Create the handler in packages/myco/src/tools/my-new-tool.ts:
-
Import required types and client:
import type { DaemonClient } from '@myco/hooks/client.js';
import { buildEndpoint } from './shared.js';
import { ToolFailure } from './error.js';
-
Define input and result types:
interface MyNewToolInput {
param_name: string;
optional_param?: number;
}
interface MyNewToolResult {
id: string;
status: string;
}
-
Implement the handler function with canonical error handling:
export async function handleMyNewTool(
input: MyNewToolInput,
client: DaemonClient,
): Promise<MyNewToolResult> {
const { param_name, optional_param = defaultValue } = input;
try {
const endpoint = buildEndpoint('/api/some-operation', {
param_name,
optional_param,
});
response = client.(endpoint);
response ;
} (error) {
(, {
: error,
: { param_name, optional_param },
: ,
});
}
}
Procedure C: Multi-Transport Registration
Register the tool once in the shared tool runtime. Stdio MCP, HTTP MCP, and the CLI all call through packages/myco/src/tools/index.ts; do not add transport-specific switch cases for normal tools.
-
Shared runtime registration in packages/myco/src/tools/index.ts:
[TOOL_MY_NEW_TOOL, async () => {
const { handleMyNewTool } = await import('./my-new-tool.js');
return {
handle: (input, client) => handleMyNewTool(input as MyNewToolInput, client),
summarize: (input, result) => ({ param_name: input.param_name }),
};
}],
-
MCP stdio and HTTP registration — packages/myco/src/mcp/server.ts exposes the shared definitions from packages/myco/src/tools/definitions.ts and dispatches calls through createMycoTools(...).
-
CLI registration — myco tool list and myco tool call use the same shared runtime, so the tool becomes available there after it is in TOOL_DEFINITIONS and HANDLERS.
-
Test multi-transport availability — verify tool appears in MCP client tool list, HTTP MCP endpoints, and CLI help for all configured transports.
-
Configure conditional enablement — Collective tools are automatically enabled/disabled based on collectiveEnabled flag across all transports. Local tools are always available.
Procedure D: Documentation Bundling and Regeneration
Each tool carries inline SKILL.md documentation bundled at compile time across all transports:
-
Write clear tool documentation covering:
- When to use this tool vs alternatives
- Parameter meanings and examples
- Expected response format
- Common usage patterns
- Transport-specific considerations (MCP stdio vs HTTP vs CLI)
-
Bundle at build time — documentation is compiled into handlers during build process and shared across transports.
-
Regenerate after schema changes:
npm run build
-
Verify agent-visible docs — test that agents receive current parameter names and descriptions across all transport types, not stale snapshots.
-
Never ship handler changes without doc updates — mismatched documentation causes agents to call tools with wrong parameters across any transport.
Procedure E: Anti-Drift Testing Patterns
Implement systematic checks to catch schema-handler-documentation drift across the shared tool-runtime:
-
Create test file template (example: packages/myco/src/tools/definitions.test.ts):
import { describe, test, expect } from 'vitest';
import { TOOL_DEFINITIONS, COLLECTIVE_TOOL_DEFINITIONS } from './definitions.js';
import * as handlers from './index.js';
-
Schema-handler parameter alignment test:
test('all schema parameters referenced in handler source', () => {
const allTools = [...TOOL_DEFINITIONS, ...COLLECTIVE_TOOL_DEFINITIONS];
for (const tool of allTools) {
const handlerName = getHandlerNameForTool(tool.name);
const handler = handlers[handlerName];
if (!handler) continue;
const schemaParams = Object.keys(tool.inputSchema.properties || {});
const handlerSource = handler.toString();
schemaParams.forEach(param => {
expect(handlerSource).toContain(param);
});
}
});
-
Handler-schema synchronization test:
Procedure F: Stub vs Documented Tool Discipline
Handle incomplete or placeholder tools appropriately:
-
Mark stubs explicitly in schema description:
{
name: TOOL_MY_STUB,
description: '[STUB] This tool is registered but not yet implemented. Returns placeholder response only.',
}
-
Implement stub handlers that return consistent "not implemented" responses:
export async function handleMyStub(): Promise<{ status: string }> {
return { status: 'not_implemented' };
}
-
Never document stubs as working tools — agents should know when functionality is incomplete across all transports.
-
Test stub behavior — ensure stubs return consistent responses rather than errors across MCP stdio, HTTP MCP, and CLI.
-
Remove or implement — stubs confuse agents across all transports. Either complete the implementation or remove from schema entirely.
Procedure G: Cloud vs Local Placement Decisions
Decide whether new tools belong in local or cloud MCP surface:
-
Default to local-only — new tools go in TOOL_DEFINITIONS unless they meet cloud criteria.
-
Promote to cloud surface only if tool is:
- Semantically read-only (no vault writes)
- Safe for federation (no sensitive data exposure)
- Required for cross-project Collective operations
-
Use COLLECTIVE_TOOL_DEFINITIONS for tools that require Collective connection state.
-
Test both surfaces — verify tools work correctly in local MCP and (if applicable) cloud federation.
-
Document placement rationale — explain why tool belongs in its chosen surface.
Procedure H: Skill Lifecycle Tool Registration Patterns
The skill lifecycle system requires specific tools that follow domain-specific registration patterns:
-
Register skill candidate management tools:
export const TOOL_SKILL_CANDIDATES = 'myco_skill_candidates';
{
name: TOOL_SKILL_CANDIDATES,
description: 'Manage skill candidates (identified topics that may become skills). Supports list, get, create, and update actions.',
cortex: {
guidance: 'Use for candidate discovery, approval workflows, and candidate lifecycle management',
priority: 80,
requiresTeam: false,
requiresCollective: false,
},
annotations: {
readOnlyHint: false,
destructiveHint: false,
idempotentHint: false,
openWorldHint: false,
},
inputSchema: {
type: 'object',
properties: {
action: {
type: 'string',
description: 'Action to perform: list, get, create, update, delete'
},
id: {
type: 'string',
description: 'Candidate ID (required for get/update)'
},
topic: {
type: 'string',
description:
},
: {
: ,
:
},
: {
: ,
:
}
},
: []
}
}
Procedure I: Shared Tool-Runtime Integration
Integrate with the shared tool-runtime supporting multiple transports:
-
Configure transport-specific behaviors:
const formatResponse = (result: any, transport: 'mcp' | 'http' | 'cli') => {
switch (transport) {
case 'mcp': return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
case 'http': return result;
case 'cli': return formatCliOutput(result);
}
};
-
Handle transport-specific authentication — MCP stdio uses connection-level auth, HTTP MCP uses token auth, CLI uses file-based auth.
-
Implement transport-aware logging:
logActivity(TOOL_NAME, {
...params,
transport: 'mcp|http|cli',
duration_ms: Date.now() - start
});
-
Test cross-transport consistency — verify same input produces equivalent results across MCP stdio, HTTP MCP, and CLI transports.
-
Document transport differences — note any transport-specific behaviors or limitations in tool documentation.
Procedure J: Grove Migration Context Handling
Handle project context changes with Grove migration architecture:
-
Update context injection patterns for Grove migration compatibility:
const context = await injectProjectContext(sessionId);
const context = await injectGroveContext(sessionId, {
fallbackToLocal: true,
respectGroveConfig: true,
});
-
Handle Grove project boundaries when tools access cross-project resources:
export async function handleCrossProjectTool(
input: CrossProjectInput,
client: DaemonClient,
): Promise<CrossProjectResult> {
const groveAccess = await client.get('/api/grove/validate-access', {
targetProject: input.project_id,
operation: 'read',
});
if (!groveAccess.allowed) {
throw new ToolFailure('Grove access denied', {
context: { project_id: input.project_id, reason: groveAccess.reason },
: ,
});
}
(input, client);
}
Cross-Cutting Gotchas
Silent parameter drops: When schema defines a parameter but handler ignores it, agents receive no error — their input is silently dropped. This is the most common drift failure.
Documentation lag: Bundled SKILL.md becomes stale when handlers change. Always regenerate documentation after schema or handler modifications.
Cloud surface leakage: Write operations must never leak to cloud MCP surface. Default to local-only; promote to cloud only with explicit read-only verification.
Validation vs runtime divergence: Schema validation passes but handler expects different parameter structure. Test actual invocations, not just schema validation.
Collective conditional enablement: collective_* tools are enabled by Collective connection state. Test both connected and disconnected scenarios across all transports.
Tool name consistency: Use myco_ prefix for standard tools, collective_ prefix for Collective-dependent tools. Avoid generic names that conflict with other MCP servers.
Handler signature mismatch: All handlers must accept (input, client) parameters. Missing DaemonClient parameter causes registration failures.
Cross-runtime schema compatibility: OpenAI strict mode and Zod refinement patterns cause silent registration failures. Use plain JSON Schema types with descriptive documentation instead of complex validation constructs.
Skill tool registration gaps: Skill lifecycle operations require complete tool registration (candidates, records, write_skill) — missing any component breaks agent workflows. Always register skill tools as a complete set.
Shared tool-runtime path shifts: Tool definitions live in packages/myco/src/tools/definitions.ts. Update import paths and test references when the shared runtime moves.
Multi-transport registration complexity: Shared tool-runtime requires consistent registration across MCP stdio, HTTP MCP, and CLI transports. Test all transports when adding new tools.
Transport-specific error handling: Different transports expect different response formats. Implement transport-aware error formatting to prevent agent confusion.
ToolFailure anti-pattern: Never throw raw Error objects from handlers — always wrap with ToolFailure interface for consistent agent error handling. Missing structured error context causes agent confusion across all transports.
Code duplication across tool surface: Avoid copy-pasting handler patterns between tools. Extract shared utilities to packages/myco/src/tools/shared.ts and import consistently. Duplicated validation logic, error handling, and response formatting patterns create maintenance burden and drift risks across the unified tool surface. Use composition patterns instead:
function handleToolA(input) {
if (!input.session_id || !isValidUuid(input.session_id)) {
throw new ToolFailure('Invalid session_id');
}
}
function handleToolB(input) {
if (!input.session_id || !isValidUuid(input.session_id)) {
throw new ToolFailure('Invalid session_id');
}
}
import { validateSessionId, validateRequiredString } from './shared.js';
function handleToolA(input) {
validateSessionId(input.session_id);
}
function handleToolB(input) {
validateSessionId(input.session_id);
}
Grove context injection failures: Tools accessing project context must handle Grove migration gracefully. Missing Grove-aware context injection causes failures in Grove environments while working in traditional project structures, creating environment-specific bugs.