| name | mcp-builder |
| description | Build MCP (Model Context Protocol) clients to connect AI agents with external tools and data sources. Use when building integrations with MCP servers. |
| license | MIT |
| compatibility | Requires TypeScript, Node.js 18+ |
| metadata | {"author":"copilot-sdk-samples","version":"1.0"} |
MCP Builder Skill
Build MCP (Model Context Protocol) clients that connect AI agents with external tools and data sources.
When to Use This Skill
Use this skill when:
- Building a client to connect to an MCP server
- Integrating AI agents with external data sources via MCP
- Creating typed wrappers around MCP tool calls
- The user mentions "MCP", "Model Context Protocol", or needs to connect to an MCP endpoint
MCP Protocol Overview
MCP is an open protocol for connecting AI models to external tools and data. Key concepts:
- MCP Server: Exposes tools, resources, and prompts via JSON-RPC over HTTP (streamable) or stdio
- MCP Client: Connects to servers, discovers tools, and executes tool calls
- Tools: Functions the model can invoke (e.g.,
search_docs, fetch_page)
- Resources: Data the model can read (e.g.,
docs://azure/overview)
Implementation Pattern
Follow this pattern when building MCP clients:
1. Define the Interface
export interface MCPClientConfig {
mode: "mock" | "live";
endpoint?: string;
}
export interface MyMCPClient {
readonly mode: "mock" | "live";
readonly isInitialized: boolean;
initialize(): Promise<ConnectorResult<void>>;
dispose(): Promise<void>;
toolName(args: ToolArgs): Promise<ConnectorResult<ToolResult>>;
}
2. Implement Mock Client First
Always create a mock implementation that works without network calls:
class MockMyMCPClient implements MyMCPClient {
readonly mode = "mock" as const;
private _isInitialized = false;
get isInitialized(): boolean {
return this._isInitialized;
}
async initialize(): Promise<ConnectorResult<void>> {
this._isInitialized = true;
return success(undefined);
}
async dispose(): Promise<void> {
this._isInitialized = false;
}
async toolName(args: ToolArgs): Promise<ConnectorResult<ToolResult>> {
if (!this._isInitialized) {
return failure({
code: ErrorCodes.NOT_INITIALIZED,
message: "Client not initialized",
});
}
({
});
}
}
3. Use ConnectorResult Pattern
All methods must return ConnectorResult<T>:
import {
ConnectorResult,
success,
failure,
ErrorCodes,
} from "../shared/connectors/types.js";
return success(data);
return failure({
code: ErrorCodes.NOT_FOUND,
message: "Resource not found",
});
4. Factory Function
Export a factory function:
export function createMyMCPClient(config: MCPClientConfig): MyMCPClient {
if (config.mode === "mock") {
return new MockMyMCPClient(config);
}
return new LiveMyMCPClient(config);
}
Example: Microsoft Learn MCP Server
The Microsoft Learn MCP Server (https://learn.microsoft.com/api/mcp) provides:
Tools
microsoft_docs_search - Search Microsoft Learn documentation
microsoft_docs_fetch - Fetch and convert a page to markdown
microsoft_code_sample_search - Search code samples
Client Implementation
export interface DocSearchResult {
title: string;
url: string;
snippet: string;
}
export interface MSLearnMCPClient {
readonly mode: "mock" | "live";
readonly isInitialized: boolean;
initialize(): Promise<ConnectorResult<void>>;
dispose(): Promise<void>;
searchDocs(query: string): Promise<ConnectorResult<DocSearchResult[]>>;
fetchDoc(url: string): Promise<ConnectorResult<string>>;
searchCodeSamples(
query: string,
language?: string,
): Promise<ConnectorResult<CodeSampleResult[]>>;
}
Testing Requirements
Every MCP client must include:
- Unit tests using Vitest
- Tests for initialization/disposal
- Tests for each tool method
- Tests for error handling (NOT_INITIALIZED, etc.)
describe("MyMCPClient", () => {
let client: MyMCPClient;
beforeEach(async () => {
client = createMyMCPClient({ mode: "mock" });
await client.initialize();
});
afterEach(async () => {
await client.dispose();
});
it("should initialize successfully", () => {
expect(client.isInitialized).toBe(true);
});
it("should fail when not initialized", async () => {
const uninit = createMyMCPClient({ mode: "mock" });
const result = await uninit.toolName({});
expect(result.success).toBe(false);
expect(result.error?.code).toBe("NOT_INITIALIZED");
});
});
File Structure
shared/connectors/<name>/
├── client.ts # Client implementation
└── index.ts # Public exports
test/connectors/
└── <name>.test.ts
Acceptance Criteria
A valid MCP client implementation must:
- ✅ Export a factory function
create<Name>Client(config)
- ✅ Support both
mock and live modes
- ✅ Use
ConnectorResult<T> for all async operations
- ✅ Have
isInitialized property
- ✅ Return
NOT_INITIALIZED error when called before initialize()
- ✅ Include comprehensive unit tests
- ✅ Follow existing connector patterns in the codebase