adaptive-mcp
Health Warn
- License — License: MIT
- Description — Repository has a description
- Active repo — Last push 0 days ago
- Low visibility — Only 6 GitHub stars
Code Warn
- process.env — Environment variable access in examples/src/client.ts
Permissions Pass
- Permissions — No dangerous permissions requested
No AI report is available for this listing yet.
observe how MCP tools are used, learn from real signal, and let clients self-govern with derived metadata
Adaptive MCP
Status: experimental. The packages are published, but the API may shift
before 1.0.
Adaptive MCP is a runtime ecosystem that learns how MCP tools are actually used
and helps runtimes adapt to that behavior over time. It observes tool
usage, attaches learned metadata to existing MCP primitives, and lets clients
govern themselves from real signal.
I introduced this project in the talk session
"Self-Improving MCP Agents" at the MCP Dev Summit in Seoul (2026).
Check session details & schedule.
Quick start
Requires Node 22+ (Node 26 recommended) and pnpm 11+
git clone https://github.com/kemalelmizan/adaptive-mcp
cd adaptive-mcp
pnpm install
pnpm -r run build
Adaptation loop
The whole loop (observe, evaluate, derive view) runs locally with no server
or transport. From the repo root:
cd examples
pnpm quickstart
That runs examples/src/quickstart.ts, which
feeds a few deploy_service calls through the learning loop and prints the
derived tools-metadata.yaml view:
import { MemoryStore } from "@adaptivemcp/memory";
import { TelemetryRecorder, MemoryBackedTelemetryStore } from "@adaptivemcp/telemetry";
import { Evaluator } from "@adaptivemcp/evaluation";
import { ExtensionController } from "@adaptivemcp/extension";
const memory = new MemoryStore(); // SQLite store
const telemetry = new TelemetryRecorder({ store: new MemoryBackedTelemetryStore(memory) });
const evaluator = new Evaluator({ memory });
const extension = new ExtensionController({ memory, yamlPath: "tools-metadata.yaml" });
// Observe a realistic stream of calls. The point of the telemetry layer is
// that it learns from *varied* signal — different tools, latency jitter, and
// the occasional failure. A loop that replays the same event 20× teaches the
// evaluator nothing (flat 0% failure rate, flat latency). Here we mix a healthy
// tool, a heavier one, and a deploy that hits a flaky window so the evaluator
// actually has something to learn.
for (let i = 0; i < 40; i++) {
telemetry.complete({ toolName: "search_customer", serverName: "crm" }, { durationMs: 120 + Math.round(Math.random() * 40) });
}
for (let i = 0; i < 15; i++) {
telemetry.complete({ toolName: "generate_report", serverName: "analytics" }, { durationMs: 1800 + Math.round(Math.random() * 300), cost: { amount: 0.012, currency: "USD" } });
}
for (let i = 0; i < 25; i++) {
telemetry.complete({ toolName: "deploy_service", serverName: "demo" }, { durationMs: 900 + Math.round(Math.random() * 150) });
}
// A flaky deploy window: ~40% of these blow up, so the evaluator flags
// deploy_service as flaky and the approval gate will ask for confirmation.
for (let i = 0; i < 15; i++) {
if (Math.random() < 0.4) {
telemetry.fail({ toolName: "deploy_service", serverName: "demo" }, { message: "upstream timeout", code: "ETIMEDOUT" }, { durationMs: 1100 });
} else {
telemetry.complete({ toolName: "deploy_service", serverName: "demo" }, { durationMs: 1100 });
}
}
evaluator.evaluateAll(); // store stats → insights → store
extension.sync(); // store → tools-metadata.yaml (and the MCP resource text)
console.log(extension.resourceText());
Run the MCP server + client example
cd examples
pnpm client # real stdio client that spawns the server and reads the resource
pnpm server # or start the server alone (blocks on stdio)
Run the scenarios
cd examples
pnpm scenario # improvement over time (healthy → flaky → fixed)
pnpm scenario:store # the store holds the metadata; YAML is derived
pnpm scenario:insights # telemetry → evaluation → insights
pnpm scenario:annotation # human annotation vs. learned insight
pnpm scenario:adaptive # full stack: routing + orchestration + approval + thin-client
Architecture
The adaptation loop runs entirely on the client/runtime side:
flowchart TD
ToolExec["Tool execution (MCP server)"] --> Telemetry
Telemetry -->|records event| Memory["MemoryStore (SQLite)"]
Memory --> Eval["Evaluation"]
Eval -->|insights| Memory
Telemetry --> Ext["ExtensionController"]
Memory --> Ext
Eval --> Ext
Ext -->|reads the store| YAML["tools-metadata.yaml (derived view)"]
Ext --> Resource["MCP resource: dev.adaptivemcp/tools-metadata"]
Data always flows in one direction: event → MemoryStore → derived
YAML view. The YAML is never edited directly; it is recomputed from the store
whenever metadata changes.
Design constraints
- MCP sets the contract; Adaptive MCP learns the behavior. MCP answers "what
can the model do?"; Adaptive MCP answers "what have we learned about how those
capabilities are actually used?" and turns that into metadata, not new
primitives. - Enrich, don't replace. No first-class
adaptiveTool,adaptiveSkill,adaptiveIntent, oradaptiveWorkflowconcepts. Adaptive MCP operates on MCP
primitives (tools, resources) as intentional boundaries. - Avoid coupling to implementation details. Packages must not depend on
shell, filesystem paths, processes, sockets, or HTTP as first-class concepts.
Those remain implementation details of the host. - Middleware is operational machinery. Telemetry, evaluation, memory,
routing, and approval exist to observe, evaluate, remember, route, and
recommend. Business logic belongs in middleware packages; the client stays
thin. - Stateless servers, accumulating clients. MCP servers are lightweight and
replaceable. Clients accumulate knowledge in a local source of truth.
Relationship to MCP
Unofficial project. Adaptive MCP is an independent, personal experiment.
It is not affiliated with, endorsed by, or maintained by the Model Context
Protocol project, its stewards, or any vendor. Thedev.adaptivemcp/extension
namespace is a reversed-domain identifier of the project domain (adaptivemcp.dev)
and is used in the spirit of, but not as part of, any official MCP extension.
The Model Context Protocol (MCP) is an open
standard that lets applications provide context and capabilities (tools,
resources, prompts) to language models in a uniform way. MCP answers "what can
the model do?" and standardizes the capabilities a server exposes.
Adaptive MCP builds on MCP rather than beside or beneath it. It does
not fork, extend, or replace the protocol; it observes how MCP tools are actually
used and attaches learned metadata (annotations, insights, recommendations) to
the existing MCP primitives. Concretely:
- MCP servers stay standard and stateless; Adaptive MCP adds a client-side
learning loop and a single derived resource (dev.adaptivemcp/tools-metadata)
that a server may publish to govern tool adaptation. - The pattern is a server-governed resource that clients read and report
against, degrading gracefully on any host that ignores it. Our draft is atdocs/sep-2133-tools-metadata.md. - Because the
dev.adaptivemcp/prefix is our own reversed domain, this is an
unofficial extension. It requires no changes to MCP itself and works with
any compliant MCP server/client.
Data model
All metadata is persisted in a SQLite store (node:sqlite). The schema is a
single tools table keyed by the composite (tool_name, server_name) pair —
not tool_name alone, since two different MCP servers can expose a tool with
the same name, and a tool_name-only key would let one server's record
silently overwrite the other's:
| Column | Type | Contents |
|---|---|---|
tool_name |
TEXT (PK) | Tool identifier |
server_name |
TEXT (PK) | Originating MCP server ('' if unknown) |
annotation |
JSON | Static, human-written Annotation |
insights |
JSON | Learned Insight[] |
recommendations |
JSON | Suggested Recommendation[] |
stats |
JSON | Accumulated ToolStats |
updated_at |
TEXT | ISO-8601 timestamp |
Core types (@adaptivemcp/spec)
Annotation: static, human-authored metadata (risk,owner,tags,description). Never changes on its own.Insight: learned from observed behavior (key,value,confidence,sourceofevaluationortelemetry,sampleSize). Upserted by key.Recommendation: suggested adaptation (typeofmodel,approval,workflow, orrouting, pluspayload,rationale,confidence). Written by the routing/orchestration/approval packages.ToolStats:invocations,failures,failureRate,avgDurationMs,totalCost,lastObservedAt. Folded from each execution event.ToolRecord: the aggregate row (toolName,serverName,annotation,insights,recommendations,stats,updatedAt).
Derived YAML view (tools-metadata.yaml)
The ExtensionController projects each ToolRecord into a ToolMetadataView
and serializes the document with js-yaml. The view is the machine- and
human-readable projection consumed by out-of-band MCP clients.
Packages
| Package | Responsibility |
|---|---|
@adaptivemcp/spec |
Extension identifiers (dev.adaptivemcp/ reversed-domain namespace), event schemas, shared types |
@adaptivemcp/memory |
SQLite store (MemoryStore) over node:sqlite |
@adaptivemcp/telemetry |
TelemetryRecorder + memory-backed store + stat queries |
@adaptivemcp/evaluation |
Evaluator emits observed_failure_rate / avg_duration_ms insights |
@adaptivemcp/extension |
ExtensionController derives + writes the YAML view and exposes the MCP resource |
@adaptivemcp/routing |
Router: model selection + budget guardrails |
@adaptivemcp/orchestration |
Orchestrator: retry-policy (workflow) recommendations |
@adaptivemcp/approval |
ApprovalGate: enforcement (allow / deny / require_confirmation) |
@adaptivemcp/thin-client |
ThinClient: client-side execution loop with gate + retry |
@adaptivemcp/graph-analysis |
GraphAnalyzer: causal cascade, bottleneck/anti-pattern detection, and forecasting over execution DAGs |
@adaptivemcp/middleware |
MiddlewareChain: pluggable hooks (beforeCall/afterCall/onError/contributeView) for transform/gate/inject-auth/observe concerns |
@adaptivemcp/mcp-binary |
Wraps an existing CLI binary as an MCP server over stdio — the only sanctioned shell-out layer |
Adaptation behaviors
- Routing (
Router.routeTool): picks the cheapest model meeting the
observed latency/failure profile; emits aroutingrecommendation when a
tool/server approaches or exceeds its cost budget (≥ 80% of limit). - Orchestration (
Orchestrator.planTool): whenfailureRate ≥ flakyThreshold(default 0.1), writes aworkflowrecommendation with a retry
policy scaled to the failure rate (capped atmaxAttempts = 6). - Approval (
ApprovalGate.gate): returnsdenyfor denied tools,require_confirmationfor high-risk annotations or flaky tools (failure rate
≥flakyFailureRate, default 0.2, afterminInvocations), elseallow.
Writes anapprovalrecommendation withpayload: { decision }. - Thin client (
ThinClient.run): consults the gate, then executes with the
store-derived retry policy (or default). Records the outcome back to the store.
The tools-metadata extension resource
MCP formalizes the server contract and lets the server govern how clients
interact with its primitives, the same direction as the Prompts primitive
(server authors, client discovers and applies). Adaptive MCP adopts that pattern:
the server governs tool adaptation by publishing policy, and the client is
the executor that learns dynamically and reports observations back.
Adaptive MCP proposes a narrow, server-governed extension, a single resource
the server publishes so clients can read (and report against) learned tool
metadata. The proposal (draft) lives atdocs/sep-2133-tools-metadata.md.
| Field | Value |
|---|---|
| URI | dev.adaptivemcp/tools-metadata |
| MIME type | application/yaml |
| Scope | server governance (annotations, budgets, required approvals) + client-reported observations |
The dev.adaptivemcp/ prefix is the reversed domain of adaptivemcp.dev (owned
by the author), using the reversed-domain namespace convention. The client learning
machinery (@adaptivemcp/telemetry, evaluation, routing, orchestration,approval, thin-client) is the executor of this policy, not part of the
extension's server contract.
The derived YAML view is exposed as the MCP resourcedev.adaptivemcp/tools-metadata (mime type application/yaml).
Advertising the extension in initialize
The @modelcontextprotocol/sdk (v1.29) includes extensions in itsServerCapabilities schema, so a server can advertise the extension ininitialize via capabilities.extensions. Adaptive MCP's example server does
not rely on that negotiation. It exposes the metadata as a plain
resource via server.registerResource(...), the standard MCP approach that
degrades gracefully on any host that ignores unknown resources:
- the resource is registered directly via
server.registerResource(...); - the
EXTENSION_NAMESPACE/TOOLS_METADATA_EXTENSIONconstants in@adaptivemcp/specprovide the canonical identifier for any host that wants to
advertise it throughcapabilities.extensions.
When a server wants to advertise it explicitly, it can pass the capability:
// conceptual: advertise the extension in initialize
const server = new McpServer({ name: "adaptive-example", version: "0.1.0" });
server.registerResource("tools-metadata", "dev.adaptivemcp/tools-metadata", {
title: "Adaptive MCP Tools Metadata",
mimeType: "application/yaml",
}, async (uri) => ({ contents: [{ uri: uri.href, mimeType: "application/yaml", text: runtime.extension.resourceText() }] }));
// and advertise in initialize:
// capabilities: { extensions: { "dev.adaptivemcp/tools-metadata": {} } }
Examples
The examples/ directory is a runnable tour of every Adaptive
MCP package. It stands up a real MCP server + client, registers tools, and
attaches the Adaptive MCP extension so a tools-metadata.yaml view is derived
automatically from a SQLite store.
- Full walkthrough.
examples/README.mdwalks
through three worked examples:- A minimal MCP server with the Adaptive extension
registersdeploy_service/search_customertools plus thedev.adaptivemcp/tools-metadataresource. - An MCP client that reads the derived view
connects over stdio, calls tools, and reads the YAML resource. - The adaptation loop, locally
AdaptiveRuntimewires the packages together without spawning a server.
- A minimal MCP server with the Adaptive extension
- Scenarios. Small, focused demos of one or two packages each
(source):scenario.js: improvement over time (healthy, then flaky, then fixed); the YAML
view evolves automatically.scenarios/store.js: the SQLite MemoryStore is the store; the YAML is a pure
projection.scenarios/insights.js: telemetry folds events into the store; evaluation
emitsobserved_failure_rate/avg_duration_msinsights.scenarios/annotation.js: humanAnnotation(static) vs. learnedInsight(dynamic) side by side.scenarios/adaptive.js: full stack (routing, orchestration, approval,
thin-client).
- Sample YAML views. Committed, illustrative examples in
examples/yaml/.
npm packages
Adaptive MCP publishes its core libraries under the @adaptivemcp npm
organization. The packages are
dependency-light and follow the same boundaries as the architecture above.
Published (@adaptivemcp/*))
All packages below are published to npm (see PUBLISHABLE_PACKAGES inscripts/lib/workspace.ts), released with thescripts/release.ts flow (seedocs/RELEASE.md for the full release runbook). The table
is generated from each package.json by pnpm docs; the version column is a
live npm badge.
Not yet published
@adaptivemcp/graph-analysis— fully implemented and tested (GraphAnalyzer:
causal cascade, anti-pattern detection, workflow forecasting), and already a
dependency ofrouting/evaluation/opencode-plugin, but not yet added toPUBLISHABLE_PACKAGES.@adaptivemcp/opencode-plugin— an experimental adapter mapping
OpenCode's hook system onto Adaptive MCP. It is not
published, has no test coverage, and has not been validated against a real
OpenCode host — treat it as a reference implementation, not a supported
integration.
How to build, test, and run
Requires Node 22+ (Node 26 recommended; the node:sqlite module is available without the--experimental-sqlite flag) and pnpm 11+.
pnpm install
pnpm -r run build # compile all packages + examples
pnpm test # run the Vitest suite (unit + integration)
pnpm lint # ESLint
Testing
The suite (vitest) covers every package plus an end-to-end integration test:
- Unit:
memory(store folding),evaluation(insight thresholds),extension(view projection + YAML stability),routing(model + budget),orchestration(retry scaling),approval(gate decisions),thin-client
(gate + retry execution). - Integration:
examples/src/runtime.test.tsdrivesAdaptiveRuntime
throughobserveCompletedand asserts the derived store state, the
recommendation types, the approval gate decision, and YAML stability/disk
write.
node:sqlite is loaded via a Vitest alias shim (vitest.sqlite-shim.mjs)
because Vite 5.x does not recognize the builtin as external; at runtime Node 26
resolves it natively.
Repository layout
adaptive-mcp/
├── packages/ # @adaptivemcp/* library packages
├── examples/ # runnable server, client, and scenarios
├── apps/ # docs / playground (scaffolds)
├── docs/ # ROADMAP.md implementation plan
├── scripts/
├── vitest.config.ts
├── vitest.sqlite-shim.mjs
├── AGENTS.md
└── README.md
Status
The core adaptation loop (telemetry → evaluation → memory → extension) plus
the pluggable middleware chain are implemented and wired intoAdaptiveRuntime. graph-analysis (execution-graph intelligence) andthin-client (client-side execution loop + graph tracking) are implemented
and tested but consumed separately, not through AdaptiveRuntime itself. Theexamples scenarios validate the full adaptation loop end to end. Seedocs/ROADMAP.md for the phased status and examples/README.md for the
scenario walkthrough.
This project was introduced publicly in the talk "Self-Improving MCP Agents"
at the MCP Dev Summit Seoul 2026.
What's next
See docs/ROADMAP.md Phase 6 for the full, risk/effort-ordered list of planned
work (new insight types, multi-server aggregation, conformance scenarios, a
real-host adapter, and more).
License
Released under the MIT License. Copyright (c) 2026 Kemal Elmizan.
Reviews (0)
Sign in to leave a review.
Leave a reviewNo results found