agentsclimarketplace

Mcp tool intent metadata injection

Skill kjuhwa/skills-hub/skills/mcp-integration/mcp-tool-intent-metadata-injection

Inject a hidden _intent / _displayName field into every MCP tool schema via a fetch interceptor so large-response summarization and UI rendering have context that the LLM never sees.From its SKILL.md

Install
npx -y skills add kjuhwa/skills-hub --skill mcp-tool-intent-metadata-injection

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

3.2 KB, 606 tokens by cl100k_base, as published. Nobody here has run it

MCP tool intent-metadata injection via fetch interceptor

When to use

  • Large MCP tool responses (60KB+) need automatic summarization, but summarization needs to know why the user called the tool.
  • You have an LLM SDK that owns the fetch loop and you can't modify how tools are defined.
  • Different UI surfaces want different display names for the same underlying tool.

How it works

  1. Build a shared fetch interceptor as a standalone CJS bundle (e.g. via esbuild) - interceptor.cjs.
  2. Inject it into every Node-based SDK subprocess with --require=/path/to/interceptor.cjs (or --preload under Bun). This patches globalThis.fetch BEFORE any SDK captures it.
  3. On outgoing LLM requests, walk the tools[] array in the JSON body. For every tool, add two hidden schema fields:
    • _intent: one-liner on why the assistant is calling it.
    • _displayName: override for UI rendering.
  4. The LLM fills them in as normal schema fields. Your host code reads them off the tool_use event.
  5. On SSE response streaming, strip these fields back out before handing to the SDK - the SDK will reject unknown fields on Anthropic. For OpenAI, pass them through and let a downstream hook strip later (the two providers differ on strictness).
  6. Store per-call metadata in a toolMetadataStore keyed by tool-use ID; re-inject on the next request so the model still sees the history it wrote.

Example

// interceptor.cjs (preloaded into every SDK subprocess)
const origFetch = globalThis.fetch;
globalThis.fetch = async (input, init) => {
  if (init?.body && isClaudeMessagesRequest(input)) {
    const body = JSON.parse(init.body);
    for (const t of body.tools ?? []) {
      t.input_schema.properties._intent = { type: 'string', description: '1-line why' };
      t.input_schema.properties._displayName = { type: 'string' };
    }
    init = { ...init, body: JSON.stringify(body) };
  }
  const res = await origFetch(input, init);
  return interceptSse(res, /* strip metadata for Anthropic */);
};

Gotchas

  • Bundle as CJS with bundled deps - you're --require-ing into arbitrary Node processes that don't have your node_modules.
  • Anthropic's SSE validation is strict; you MUST strip metadata from streamed deltas before the SDK parses them.
  • Re-injection on follow-up turns is easy to forget. Store metadata by the tool-use-id and walk the messages[].content array to rewrite.
  • The LLM will "waste" tokens writing _intent values - cap the description with a firm instruction like "one short sentence, <120 chars".

What ships with it

Read from the repository

Just SKILL.md. No reference files, no scripts.

Keep looking

Skills are one crate of 326,764. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.