From 6dae9f539a49ba6ca4c87f523de6923a58de5e47 Mon Sep 17 00:00:00 2001 From: Tianqi Zhang Date: Fri, 13 Mar 2026 14:42:11 +0800 Subject: [PATCH 1/3] Fix CLI shell references and refactor MCP client defaults - Replace "via Bash" with "from the command line" in skill docs since the Learn CLI works in any shell, not just Bash - Change code block language markers from ```bash to ```sh - Rename MCP client name from "mslearn" to "learn-cli" - Centralize clientName and clientVersion defaults in context.ts instead of repeating them in every command file Co-Authored-By: Claude Opus 4.6 --- cli/src/commands/code-search.ts | 6 +----- cli/src/commands/doctor.ts | 6 +----- cli/src/commands/fetch.ts | 6 +----- cli/src/commands/search.ts | 6 +----- cli/src/context.ts | 7 ++++++- cli/src/mcp/client.ts | 2 +- skills/microsoft-code-reference/SKILL.md | 4 ++-- skills/microsoft-docs/SKILL.md | 4 ++-- skills/microsoft-skill-creator/SKILL.md | 4 ++-- 9 files changed, 17 insertions(+), 28 deletions(-) diff --git a/cli/src/commands/code-search.ts b/cli/src/commands/code-search.ts index 0302da52..b4c60279 100644 --- a/cli/src/commands/code-search.ts +++ b/cli/src/commands/code-search.ts @@ -19,11 +19,7 @@ export function registerCodeSearchCommand(program: Command, context: CliContext) .option('--json', 'Output raw JSON instead of formatted text.') .action(async (query: string, options: CodeSearchCommandOptions) => { const endpoint = resolveEndpoint(program.opts<{ endpoint?: string }>().endpoint, context.env); - const client = context.createClient({ - endpoint, - clientName: 'mslearn', - clientVersion: context.version, - }); + const client = context.createClient({ endpoint }); try { const payload = await client.searchCodeSamples(query, options.language); diff --git a/cli/src/commands/doctor.ts b/cli/src/commands/doctor.ts index 4e38ddb2..70253e9b 100644 --- a/cli/src/commands/doctor.ts +++ b/cli/src/commands/doctor.ts @@ -41,11 +41,7 @@ async function runDoctorChecks(endpoint: string, context: CliContext): Promise { const endpoint = resolveEndpoint(program.opts<{ endpoint?: string }>().endpoint, context.env); const normalizedUrl = normalizeUrl(url); - const client = context.createClient({ - endpoint, - clientName: 'mslearn', - clientVersion: context.version, - }); + const client = context.createClient({ endpoint }); try { const markdown = await client.fetchDocument(normalizedUrl); diff --git a/cli/src/commands/search.ts b/cli/src/commands/search.ts index c8424ee0..8a0a65cf 100644 --- a/cli/src/commands/search.ts +++ b/cli/src/commands/search.ts @@ -17,11 +17,7 @@ export function registerSearchCommand(program: Command, context: CliContext): vo .option('--json', 'Output raw JSON instead of formatted text.') .action(async (query: string, options: SearchCommandOptions) => { const endpoint = resolveEndpoint(program.opts<{ endpoint?: string }>().endpoint, context.env); - const client = context.createClient({ - endpoint, - clientName: 'mslearn', - clientVersion: context.version, - }); + const client = context.createClient({ endpoint }); try { const payload = await client.searchDocs(query); diff --git a/cli/src/context.ts b/cli/src/context.ts index ab8c04b6..993a41f9 100644 --- a/cli/src/context.ts +++ b/cli/src/context.ts @@ -20,6 +20,11 @@ export function createDefaultContext(version: string): CliContext { process.stderr.write(value); }, fetchImpl: globalThis.fetch.bind(globalThis) as typeof fetch, - createClient: (options) => createLearnCliClient(options), + createClient: (options) => + createLearnCliClient({ + clientName: 'learn-cli', + clientVersion: version, + ...options, + }), }; } diff --git a/cli/src/mcp/client.ts b/cli/src/mcp/client.ts index c064b69c..443227bc 100644 --- a/cli/src/mcp/client.ts +++ b/cli/src/mcp/client.ts @@ -271,7 +271,7 @@ class LearnCliClient implements LearnCliClientLike { private createDefaultSdkClient(): SdkClientLike { return new Client( { - name: this.options.clientName ?? 'mslearn', + name: this.options.clientName ?? 'learn-cli', version: this.options.clientVersion ?? '0.1.0', }, { diff --git a/skills/microsoft-code-reference/SKILL.md b/skills/microsoft-code-reference/SKILL.md index 5fb3cf8c..61bc2fe7 100644 --- a/skills/microsoft-code-reference/SKILL.md +++ b/skills/microsoft-code-reference/SKILL.md @@ -80,9 +80,9 @@ For simple lookups, step 1 alone may suffice. For complex API usage, complete al ## CLI Alternative -If the Learn MCP server is not available, use the `mslearn` CLI via Bash instead: +If the Learn MCP server is not available, use the `mslearn` CLI from the command line instead: -```bash +```sh # Run directly (no install needed) npx @microsoft/learn-cli search "BlobClient UploadAsync Azure.Storage.Blobs" diff --git a/skills/microsoft-docs/SKILL.md b/skills/microsoft-docs/SKILL.md index fabc1e2b..aaeedef0 100644 --- a/skills/microsoft-docs/SKILL.md +++ b/skills/microsoft-docs/SKILL.md @@ -58,9 +58,9 @@ Fetch after search when: ## CLI Alternative -If the Learn MCP server is not available, use the `mslearn` CLI via Bash instead: +If the Learn MCP server is not available, use the `mslearn` CLI from the command line instead: -```bash +```sh # Run directly (no install needed) npx @microsoft/learn-cli search "azure functions timeout" diff --git a/skills/microsoft-skill-creator/SKILL.md b/skills/microsoft-skill-creator/SKILL.md index 433b66c9..6193f087 100644 --- a/skills/microsoft-skill-creator/SKILL.md +++ b/skills/microsoft-skill-creator/SKILL.md @@ -39,9 +39,9 @@ skill-name/ ### CLI Alternative -If the Learn MCP server is not available, use the `mslearn` CLI via Bash instead: +If the Learn MCP server is not available, use the `mslearn` CLI from the command line instead: -```bash +```sh # Run directly (no install needed) npx @microsoft/learn-cli search "semantic kernel overview" From d3eb5c58bf90708c71b8f4301592bed8efdc60a0 Mon Sep 17 00:00:00 2001 From: Tianqi Zhang Date: Fri, 13 Mar 2026 14:53:07 +0800 Subject: [PATCH 2/3] Address review: centralize client name constant and wire fetchImpl - Export DEFAULT_CLIENT_NAME from client.ts as single source of truth - Pass context's fetchImpl to createClient so MCP client and probeEndpoint use the same fetch implementation Co-Authored-By: Claude Opus 4.6 --- cli/src/context.ts | 9 ++++++--- cli/src/mcp/client.ts | 4 +++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/cli/src/context.ts b/cli/src/context.ts index 993a41f9..b342c8b8 100644 --- a/cli/src/context.ts +++ b/cli/src/context.ts @@ -1,4 +1,4 @@ -import { createLearnCliClient, type LearnCliClientLike, type LearnClientOptions } from './mcp/client.js'; +import { createLearnCliClient, DEFAULT_CLIENT_NAME, type LearnCliClientLike, type LearnClientOptions } from './mcp/client.js'; export interface CliContext { env: NodeJS.ProcessEnv; @@ -10,6 +10,8 @@ export interface CliContext { } export function createDefaultContext(version: string): CliContext { + const fetchImpl = globalThis.fetch.bind(globalThis) as typeof fetch; + return { env: process.env, version, @@ -19,11 +21,12 @@ export function createDefaultContext(version: string): CliContext { writeErr: (value) => { process.stderr.write(value); }, - fetchImpl: globalThis.fetch.bind(globalThis) as typeof fetch, + fetchImpl, createClient: (options) => createLearnCliClient({ - clientName: 'learn-cli', + clientName: DEFAULT_CLIENT_NAME, clientVersion: version, + fetchImpl, ...options, }), }; diff --git a/cli/src/mcp/client.ts b/cli/src/mcp/client.ts index 443227bc..aa7bda48 100644 --- a/cli/src/mcp/client.ts +++ b/cli/src/mcp/client.ts @@ -7,6 +7,8 @@ import { OperationError } from '../utils/errors.js'; import { createFileLearnSessionCacheStore, type LearnSessionCacheStore } from './cache.js'; import { discoverLearnTools, type DiscoveredLearnTools, type ListedTool } from './tool-discovery.js'; +export const DEFAULT_CLIENT_NAME = 'learn-cli'; + export interface LearnClientOptions { endpoint: string; clientName?: string; @@ -271,7 +273,7 @@ class LearnCliClient implements LearnCliClientLike { private createDefaultSdkClient(): SdkClientLike { return new Client( { - name: this.options.clientName ?? 'learn-cli', + name: this.options.clientName ?? DEFAULT_CLIENT_NAME, version: this.options.clientVersion ?? '0.1.0', }, { From 68e5d3a00b0f249d6a514014972ec8ecbc25c633 Mon Sep 17 00:00:00 2001 From: Tianqi Zhang Date: Mon, 16 Mar 2026 08:16:32 +0800 Subject: [PATCH 3/3] Simplify CLI defaults: let client.ts own its defaults - Remove fetchImpl and clientName from CliContext; client.ts already defaults both internally - Make DEFAULT_CLIENT_NAME a private constant (no need to export) - Default probeEndpoint's fetchImpl parameter to globalThis.fetch - Simplify configureOutput wrappers in index.ts - context.ts only injects clientVersion (needs access to package version) Co-Authored-By: Claude Opus 4.6 --- cli/src/commands/doctor.ts | 2 +- cli/src/context.ts | 8 +------- cli/src/index.ts | 8 ++------ cli/src/mcp/client.ts | 4 ++-- cli/test/unit/cli.test.ts | 1 - 5 files changed, 6 insertions(+), 17 deletions(-) diff --git a/cli/src/commands/doctor.ts b/cli/src/commands/doctor.ts index 70253e9b..901fa931 100644 --- a/cli/src/commands/doctor.ts +++ b/cli/src/commands/doctor.ts @@ -35,7 +35,7 @@ export function registerDoctorCommand(program: Command, context: CliContext): vo async function runDoctorChecks(endpoint: string, context: CliContext): Promise { const runtimeVersion = process.versions.node; const runtimeSupported = Number.parseInt(runtimeVersion.split('.')[0] ?? '0', 10) >= 22; - const reachability = await probeEndpoint(endpoint, context.fetchImpl); + const reachability = await probeEndpoint(endpoint); const errors: string[] = []; const tools: DoctorReport['tools'] = {}; let connected = false; diff --git a/cli/src/context.ts b/cli/src/context.ts index b342c8b8..8c3e9dc0 100644 --- a/cli/src/context.ts +++ b/cli/src/context.ts @@ -1,17 +1,14 @@ -import { createLearnCliClient, DEFAULT_CLIENT_NAME, type LearnCliClientLike, type LearnClientOptions } from './mcp/client.js'; +import { createLearnCliClient, type LearnCliClientLike, type LearnClientOptions } from './mcp/client.js'; export interface CliContext { env: NodeJS.ProcessEnv; version: string; writeOut: (value: string) => void; writeErr: (value: string) => void; - fetchImpl: typeof fetch; createClient: (options: LearnClientOptions) => LearnCliClientLike; } export function createDefaultContext(version: string): CliContext { - const fetchImpl = globalThis.fetch.bind(globalThis) as typeof fetch; - return { env: process.env, version, @@ -21,12 +18,9 @@ export function createDefaultContext(version: string): CliContext { writeErr: (value) => { process.stderr.write(value); }, - fetchImpl, createClient: (options) => createLearnCliClient({ - clientName: DEFAULT_CLIENT_NAME, clientVersion: version, - fetchImpl, ...options, }), }; diff --git a/cli/src/index.ts b/cli/src/index.ts index 14f610f2..5e952ff6 100644 --- a/cli/src/index.ts +++ b/cli/src/index.ts @@ -25,12 +25,8 @@ export function createProgram(context: CliContext): Command { .addOption(new Option('--endpoint ', 'Override the Learn MCP endpoint for this command.').hideHelp()) .showHelpAfterError() .configureOutput({ - writeOut: (value) => { - context.writeOut(value); - }, - writeErr: (value) => { - context.writeErr(value); - }, + writeOut: context.writeOut, + writeErr: context.writeErr, outputError: (value, write) => { write(value); }, diff --git a/cli/src/mcp/client.ts b/cli/src/mcp/client.ts index aa7bda48..1566fd8a 100644 --- a/cli/src/mcp/client.ts +++ b/cli/src/mcp/client.ts @@ -7,7 +7,7 @@ import { OperationError } from '../utils/errors.js'; import { createFileLearnSessionCacheStore, type LearnSessionCacheStore } from './cache.js'; import { discoverLearnTools, type DiscoveredLearnTools, type ListedTool } from './tool-discovery.js'; -export const DEFAULT_CLIENT_NAME = 'learn-cli'; +const DEFAULT_CLIENT_NAME = 'learn-cli'; export interface LearnClientOptions { endpoint: string; @@ -45,7 +45,7 @@ interface TransportLike { close(): Promise; } -export async function probeEndpoint(endpoint: string, fetchImpl: typeof fetch): Promise { +export async function probeEndpoint(endpoint: string, fetchImpl: typeof fetch = globalThis.fetch): Promise { try { const response = await fetchImpl(endpoint, { method: 'GET', diff --git a/cli/test/unit/cli.test.ts b/cli/test/unit/cli.test.ts index 49b79382..1a639bf5 100644 --- a/cli/test/unit/cli.test.ts +++ b/cli/test/unit/cli.test.ts @@ -37,7 +37,6 @@ function createTestContext(client: LearnCliClientLike): { writeErr: (value) => { stderr.push(value); }, - fetchImpl: vi.fn(async () => new Response(null, { status: 405 })) as unknown as typeof fetch, createClient: () => client, }, stdout,