Skip to main content
Glama
lyupro
by lyupro

Reload skills

skills__reload

Rescan all configured skill folders and return added/removed skills along with per-file errors. Optionally validate a specific folder is configured.

Instructions

Force a full rescan of all configured folders, returning the diff (added/removed) and any per-file errors. Pass an optional folder name to validate it is currently configured (the rescan itself remains global).

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
folderNo

Implementation Reference

  • The handleReload function that implements the reload logic: validates optional folder argument, captures before/after registry state, invalidates metadata cache, calls rebuildRegistry, and returns diff (added/removed) plus errors.
    export async function handleReload(
      deps: ServerDeps,
      args: { folder?: string },
    ): Promise<ReloadResult> {
      try {
        if (args.folder !== undefined) {
          const absolute = resolvePath(args.folder);
          // Why: reload always rebuilds the FULL registry (every configured folder), not just
          // the named one. Validating presence preserves the API contract for callers without
          // forcing a partial-scan code path. Partial reload deferred — would require splitting
          // the conflict-resolver logic, not worth the complexity in v0.x.
          if (!deps.folders.includes(absolute)) {
            throw new Error(`reload: folder "${args.folder}" is not currently configured`);
          }
        }
    
        const before = new Set(deps.registry.getAll().map((s) => s.name));
    
        deps.metadataCache.invalidate();
    
        const errorSink: Array<{ path: string; message: string }> = [];
        const stats = await rebuildRegistry(deps, { errorSink });
    
        const added = stats.skills.filter((n) => !before.has(n));
        const removed = [...before].filter((n) => !stats.skills.includes(n)).sort();
    
        return {
          loaded: stats.skills.length,
          added,
          removed,
          errors: stats.errors,
        };
      } catch (err) {
        const msg = err instanceof Error ? err.message : String(err);
        if (msg.startsWith('reload: ')) throw err;
        throw new Error(`reload: ${msg}`);
      }
    }
  • Input schema for the reload tool: an optional 'folder' string parameter defined using Zod.
    export const reloadInputSchema = {
      folder: z.string().optional(),
    } as const;
  • ReloadResult interface defining the shape of the reload response: loaded count, added skill names, removed skill names, and per-file errors.
    export interface ReloadResult {
      /** Total skills in the registry after reload. */
      loaded: number;
      /** Skill names present after reload but not before. */
      added: string[];
      /** Skill names present before reload but not after. */
      removed: string[];
      /** Per-file errors collected during the rebuild. */
      errors: Array<{ path: string; message: string }>;
    }
  • The rebuildRegistry helper function called by handleReload. Clears the registry and content cache, rescans all configured folders, parses files, applies blacklist filter, resolves conflicts, and returns the list of skills and any errors.
    export async function rebuildRegistry(deps: ServerDeps, opts?: RebuildOptions): Promise<RebuildStats> {
      const errorSink = opts?.errorSink;
    
      deps.registry.clear();
      deps.contentCache.clear();
    
      // Collect all candidates grouped by skill name for conflict resolution.
      const candidates = new Map<string, SkillMetadata[]>();
    
      for (const folder of deps.folders) {
        let filePaths: string[];
        try {
          filePaths = await deps.scanner.scan(folder);
        } catch (err) {
          const msg = err instanceof Error ? err.message : String(err);
          if (errorSink !== undefined) {
            errorSink.push({ path: folder, message: msg });
          } else {
            console.error(`[skillforge] skipped folder ${folder}: ${msg}`);
          }
          continue;
        }
    
        for (const filePath of filePaths) {
          let content;
          try {
            content = await deps.parser.parseFile(filePath, folder);
          } catch (err) {
            const msg = err instanceof Error ? err.message : String(err);
            if (errorSink !== undefined) {
              errorSink.push({ path: filePath, message: msg });
            } else {
              console.error(`[skillforge] skipped ${filePath}: ${msg}`);
            }
            continue;
          }
    
          // Derive metadata snapshot (strip body + raw).
          const { body: _body, raw: _raw, ...metadata } = content;
          const meta: SkillMetadata = metadata;
    
          const verdict = deps.blacklistFilter.evaluate(content);
          if (!verdict.allowed) {
            const detail = verdict.reason === 'manual'
              ? 'blacklisted by name'
              : `audit hit: ${verdict.pattern}`;
            // Blacklist rejections are routine exclusions — always log to stderr, never sink.
            console.error(`[skillforge] excluded "${meta.name}" from ${filePath} — ${detail}`);
            continue;
          }
    
          const existing = candidates.get(meta.name) ?? [];
          existing.push(meta);
          candidates.set(meta.name, existing);
    
          // Store full content so resolve winner can be cached below.
          // We keep all candidates' content; winner selection happens after.
          deps.contentCache.set(meta.name + '\x00' + folder, content);
        }
      }
    
      // Resolve conflicts and register winners.
      for (const [name, group] of candidates) {
        const winner = deps.resolver.resolve(group, deps.folders);
        deps.registry.register(winner);
    
        // Retrieve the full content for the winner from the temporary keyed cache.
        const tempKey = name + '\x00' + winner.folder;
        const winnerContent = deps.contentCache.get(tempKey);
        if (winnerContent !== undefined) {
          deps.contentCache.set(name, winnerContent);
        }
        // Clean up temp keys (all candidates for this name).
        for (const candidate of group) {
          deps.contentCache.invalidate(name + '\x00' + candidate.folder);
        }
      }
    
      deps.metadataCache.markFresh();
    
      const skills = deps.registry.getAll().map((s) => s.name).sort();
      const errors = errorSink ?? [];
      return { skills, errors };
    }
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The description discloses the rescan is global and the folder parameter is for validation only, but does not mention potential side effects, auth needs, or performance impact. Since annotations are absent, more detail would be beneficial.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two sentences with no wasted words. The action and output are front-loaded. Efficient structure.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given no output schema, it describes the return value (diff and errors). The single optional parameter is explained. Could mention synchronous/asynchronous nature, but overall fairly complete.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The schema has no description for the parameter; the description adds meaning by explaining the folder parameter is for validation and the rescan remains global. This compensates for the 0% schema coverage.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the action (force a full rescan) and what it returns (diff and per-file errors). It distinguishes from siblings like skills__configure and skills__list.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

No guidance on when to use this tool vs alternatives like skills__get or skills__invoke. The description does not mention prerequisites or context.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/lyupro/skillforge-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server