| name | mcp-tool-writing |
| description | Write MCP tools for the vscode-deephaven TypeScript MCP server. Use when adding new tools, implementing tool handlers, or creating tests for MCP tools. |
| license | Complete terms in LICENSE.txt |
MCP Tool Writing for vscode-deephaven
Guide for adding MCP tools to the vscode-deephaven extension's existing TypeScript MCP server.
Overview
Specialized patterns for the vscode-deephaven MCP server. Consult the mcp-builder skill (from anthropics/skills repo) for general MCP best practices, and the local test-writing skill (.github/skills/test-writing/) for testing patterns.
Tool Implementation Pattern
1. File Structure
Create two files for each tool:
src/mcp/tools/
├── {toolName}.ts # Tool implementation
└── {toolName}.spec.ts # Tool tests
Export from src/mcp/tools/index.ts:
export * from './{toolName}';
Register in src/mcp/McpServer.ts constructor:
this.registerTool(create{ToolName}Tool(this));
2. Tool Implementation Template
See existing tools in src/mcp/tools/ for complete examples. Key structure:
import { z } from 'zod';
import type {
McpTool,
McpToolHandlerArg,
McpToolHandlerResult,
} from '../../types';
import { createMcpToolOutputSchema, McpToolResponse } from '../utils';
const spec = {
title: 'Tool Title',
description: 'What the tool does and when to use it',
inputSchema: {
param: z.string().describe('Parameter description'),
},
outputSchema: createMcpToolOutputSchema({
result: z.string().optional().describe('Result field'),
}),
} as const;
type Spec = typeof spec;
type HandlerArg = McpToolHandlerArg<Spec>;
type HandlerResult = McpToolHandlerResult<Spec>;
type ToolNameTool = McpTool<Spec>;
export function createToolNameTool({
serverManager,
}: {
serverManager: IServerManager;
}): ToolNameTool {
return {
name: 'toolName',
spec,
handler: async ({ param }: HandlerArg): Promise<HandlerResult> => {
const response = new McpToolResponse();
return response.success('Done', { result });
},
};
}
3. Spec Definition Rules
Input Schema:
- Use Zod schemas directly as object properties (NOT
z.object())
- Add descriptive
.describe() to every parameter
- Document expected formats (e.g., URLs, language IDs)
Output Schema:
- Always use
createMcpToolOutputSchema() helper
- Pass optional details shape as object of Zod schemas
- Include ALL detail properties from every response call (success, error, errorWithHint)
- Make properties required only if present in ALL code paths; otherwise optional (missing required fields in error responses cause schema errors)
- Sort detail properties alphabetically
- Add descriptions for all fields
Example:
const spec = {
title: 'Get Table Stats',
description:
'Get schema information and basic statistics for a Deephaven table',
inputSchema: {
connectionUrl: z
.string()
.describe('Connection URL (e.g., "http://localhost:10000")'),
tableName: z.string().describe('Name of the table to describe'),
},
outputSchema: createMcpToolOutputSchema({
columns: z
.array(
z.object({
name: z.string(),
type: z.string(),
description: z.string().optional(),
})
)
.optional()
.describe('Array of column definitions'),
connectionUrl: z.string().optional().describe('Connection URL'),
isRefreshing: z
.boolean()
.optional()
.describe('Whether the table is refreshing (ticking)'),
size: z.number().optional().describe('Number of rows in the table'),
tableName: z.string().optional().describe('Name of the table'),
}),
} as const;
4. Type Safety
Define type aliases for full type inference:
type Spec = typeof spec;
type HandlerArg = McpToolHandlerArg<Spec>;
type HandlerResult = McpToolHandlerResult<Spec>;
type ToolNameTool = McpTool<Spec>;
5. Dependency Injection
Tools receive dependencies via constructor parameters:
export function createGetTableStatsTool({
serverManager,
}: {
serverManager: IServerManager;
}): GetTableStatsTool {
}
Dependency sources:
- Check
McpServer.ts constructor for available services to inject
- VS Code APIs can be used directly via
import * as vscode from 'vscode'
- Commands available via
import { execXxx } from '../../common/commands'
- Only request dependencies your tool actually needs
Response Patterns
Instantiate McpToolResponse at handler start to track execution time:
const response = new McpToolResponse();
return response.success('Done');
return response.success('Found 5 items', { items });
return response.successWithHint('Created', 'Next: use runCode', { url });
return response.error('Failed');
return response.error('Connection failed', error);
return response.error('Not found', null, { available: ['a', 'b'] });
return response.errorWithHint('Invalid', error, 'Use format: http://...', {
url,
});
All responses include: success, message, executionTimeMs, optional hint, optional details.
Common Patterns
1. URL Validation
import { parseUrl } from '../../util';
const parsedUrl = parseUrl(connectionUrl);
if (!parsedUrl.success) {
return response.error('Invalid URL', parsedUrl.error, {
connectionUrl,
});
}
2. Connection Management
For tools that interact with Deephaven servers:
import { getFirstConnectionOrCreate } from '../utils';
const firstConnectionResult = await getFirstConnectionOrCreate({
connectionUrl: parsedUrl.value,
serverManager,
languageId,
});
if (!firstConnectionResult.success) {
const { details, error, errorMessage, hint } = firstConnectionResult;
return response.errorWithHint(errorMessage, error, hint, details);
}
const { connection, panelUrlFormat } = firstConnectionResult;
This helper:
- Finds or creates a connection
- Handles server validation
- Provides actionable error hints
- Returns panel URL format for UI linking
3. Session and Table Access
const session = await connection.getSession();
if (!session) {
return response.error('Unable to access session', null, { connectionUrl });
}
const table: DhcType.Table = await session.getObject({
type: 'Table',
name: tableName,
});
try {
return response.success('Success', {
});
} finally {
table.close();
}
4. Resource Cleanup
Always close Deephaven resources:
const table = await session.getObject({ type: 'Table', name: tableName });
try {
} finally {
table.close();
}
Testing
See test-writing skill for comprehensive patterns. MCP-specific essentials:
import { beforeEach, describe, expect, it, vi } from 'vitest';
import {
fakeMcpToolTimings,
mcpSuccessResult,
mcpErrorResult,
} from '../utils/mcpTestUtils';
vi.mock('vscode');
beforeEach(() => {
vi.clearAllMocks();
fakeMcpToolTimings();
});
expect(result.structuredContent).toEqual(mcpSuccessResult('Done', { data }));
expect(result.structuredContent).toEqual(mcpErrorResult('Failed', { url }));
Integration Checklist
Reference
Common Imports
import type {
McpTool,
McpToolHandlerArg,
McpToolHandlerResult,
IServerManager,
IPanelService,
IDhcService,
} from '../../types';
import { parseUrl } from '../../util';
import {
createMcpToolOutputSchema,
McpToolResponse,
getFirstConnectionOrCreate,
} from '../utils';
import { z } from 'zod';
import type { dh as DhcType } from '@deephaven/jsapi-types';
import * as vscode from 'vscode';
Tool Naming Conventions
- Function name:
create{ToolName}Tool (PascalCase)
- Tool name:
{toolName} (camelCase)
- File name:
{toolName}.ts (camelCase)
- Type alias:
{ToolName}Tool (PascalCase)
Error Messages
Be specific, include context in details, provide actionable hints:
return response.errorWithHint(
'Column not found',
null,
'Use getTableStats to see available columns',
{ columnName, tableName, availableColumns: table.columns.map(c => c.name) }
);
Additional Resources
- MCP Best Practices: Consult
mcp-builder skill for general MCP patterns
- Test Writing: Consult
test-writing skill for comprehensive testing guidance
- Existing Tools: Review
src/mcp/tools/ for implementation examples
- Type Definitions: See
src/types/mcpTypes.d.ts for core types
- Utilities: See
src/mcp/utils/ for shared helpers