# FlowMCP — Complete Website Content > Normalize any data source and make it usable for AI agents. > Open Source (MIT). Install: npm install -g github:FlowMCP/flowmcp-cli Docs: https://flowmcp.github.io/docs GitHub: https://github.com/flowmcp Spec: https://raw.githubusercontent.com/FlowMCP/flowmcp-spec/refs/heads/main/spec/v3.0.0/llms.txt --- # About the Project /introduction/about ## The Problem There is a vast amount of open data in Germany and Europe — transit schedules, weather data, government records, geodata, environmental data. But it is scattered across hundreds of portals and interfaces, in different formats, with varying quality and behind different access methods. For a human, finding and using the right data is tedious. For an AI, it is nearly impossible — without prior preparation. ## Our Solution We make open data usable for AI agents through a schema system. Each schema describes how to access a data source — and brings it to a common standard. ```mermaid graph LR A[Weather API] --> S[FlowMCP Schema] B[Transit API] --> S C[Geodata API] --> S D[Government API] --> S S --> T[Unified MCP Tools] T --> AI[Any AI Agent] ``` **We do not change the data sources.** We adapt to them. The schema translates between the data source and the agent. ## The Goal > **Build your own agent in 5 minutes.** We provide validated schemas. You combine them into an agent that answers your questions with real data. Open source, self-hostable, accessible to everyone. That is our challenge — and we are working to make it a reality. Our schemas lay the foundation. The AI can adapt them and the user can develop them further for their needs. ## Who is this for? | Audience | Description | |----------|-------------| | **Individuals** | Decision support with real data — e.g. route planning, weather, local information | | **Government and public sector** | A starting point for making open data structurally accessible — not a finished product, but a foundation to build on | | **Developers** | Build your own agents, customize schemas, contribute to the ecosystem | ## Why Schemas? An AI without prior knowledge would have to analyze each data source from scratch — an enormous token cost and energy expenditure on every call. Our schema preparation is a **one-time investment**: once described, usable by every AI. This saves energy and costs — by a **factor of 10**. More about this and our convictions: Why We Do This → ## A Fast-Growing Market AI agents are developing rapidly. OpenClaw (https://docs.openclaw.ai) — an open-source AI assistant gateway — has existed only since November 2025 and has already reached over 330,000 GitHub stars, overtaking the Linux operating system (225,000 stars) in less than four months. The industry is working on security guidelines so that companies and government agencies can also work with AI agents. Our contribution: preparing and validating datasets so AI agents can use them. ## Technical Openness Our software is **model-agnostic**: It runs with any LLM. We optimize agents for one model (a necessary decision for the best possible quality), but operation on other models is easily possible. It is also **client-agnostic**: Any MCP-compatible client or CLI can use our schemas — you have free choice. More at MCP Clients & CLI. ## Where does this come from? FlowMCP started as a way to make public data sources accessible to AI agents. Today, it provides a complete framework — from schema definition to agent integration. More at Schemas and Tools --- # Why We Do This /introduction/why ## From the Web Browser Internet to the AI Internet For decades, the browser internet was dominant — people visit websites, click links, fill out forms. Alongside it existed the "hacker internet," where technically skilled users access data through APIs and scripts. With powerful language models, a third picture is now emerging: an **AI internet** that lives at its core in a chat interface. You talk to an AI — and the AI interacts with data sources and interfaces in the background. The user sees only the result. This is exactly where we come in: as a bridge between people and public data sources in this new AI internet. Publicly funded data should not only be used by large platforms, but directly benefit the people who paid for it. ## Open Protocols Instead of Closed Platforms Our project builds on an open protocol, not a closed platform. In practice, this means: the schemas we create are not tied to any specific provider. Other projects or government agencies can develop their own implementations and still use the same schema set. If an implementation fails, it can be replaced — without changing schemas or clients. This creates independence and resilience. It also means: we are not building a proprietary platform, but reusable infrastructure. ## Democratic Participation Those who make their data available help shape the basis on which AI systems make recommendations. Without structured connections, public data is left out of AI decisions — and the AI relies instead on sources the user does not control. We want citizens to be able to ask questions through an AI of their choice — with answers based on official open data sources. For example: - "What is the air quality in my neighborhood?" — The AI retrieves real measurement data and explains it in everyday language - "Which free counseling centers are near me?" — The AI lists centers with contact information - "What did my city council decide about bike lanes?" — The AI evaluates council information The user decides which sources the AI uses — not the platform. This is a fundamental difference from closed systems. ## Why Many Data Sources? A single data source answers a single question. But real questions in everyday life are never simple. "Should I bike to the dentist tomorrow?" needs weather, route, bike availability, and the calendar. None of these sources alone can answer the question. Only the combination makes the answer useful. **One data source = one answer. Many data sources = a useful answer.** Our schemas make this combination possible — without the user needing to know where the data comes from. Concrete examples: Use Cases → ## Energy Efficiency: Prepare Once, Use Forever ```mermaid graph LR subgraph Without Schema Q1[Question] --> Read[Read API docs] Read --> Parse[Parse endpoints] Parse --> Try[Trial and error] Try --> R1[Result: ~15x tokens] end subgraph With Schema Q2[Question] --> Tool[Call prepared tool] Tool --> R2[Result: 1x tokens] end ``` Without a schema, every AI would have to read and interpret the API documentation of a data source from scratch with every request. This costs thousands of tokens per request — and results are inconsistent because the AI takes different approaches depending on randomness factors. Our schema preparation is a **one-time investment**: we analyze the data source, describe it in a structured way, and every AI can then use it efficiently. A single request with a schema requires only a fraction of the tokens. Across hundreds of users and thousands of requests, this adds up to savings by a **factor of 10**. This is due to several effects: without a schema, the AI must reinterpret the API documentation with every new session. Over time, knowledge is lost through compression and must be re-researched. Additionally, without a schema, the AI takes different paths each day depending on randomness (temperature) — with a schema, access is deterministic and always the same. The principle behind it: **"Validated once, for all."** What has been carefully prepared once is available to everyone afterward — consistent, deterministic, and energy-efficient. This principle also becomes the core of our Community Hub, where the community itself can contribute and validate schemas. ## Security Through Transparency Preparation happens **publicly** — as open source. This makes it auditable and verifiable for everyone. Anyone can see how a schema accesses a data source, which parameters are used, and what comes back. This meets modern security standards and builds trust. The default path is designed to be hard to use wrong — security by design, not by documentation. ## Digital Sovereignty Users retain control: which data sources are active, which schemas are loaded. No lock-in to any single platform. The protocol is open — alternative implementations can be used if needed, without changing schemas or clients. This also applies to operations: those who want to can run our schemas on a local server — completely independent from cloud services. More at Integration. --- # For LLMs /introduction/for-llms ## Mini-Skill Copy this block into your AI chat to give it full FlowMCP context: ``` # FlowMCP FlowMCP normalizes data sources into MCP tools via declarative .mjs schemas. Open Source (MIT). Install: npm install -g github:FlowMCP/flowmcp-cli ## Documentation Docs: https://flowmcp.github.io/docs-llms.txt Spec: https://raw.githubusercontent.com/FlowMCP/flowmcp-spec/refs/heads/main/spec/v3.0.0/llms.txt Full: https://flowmcp.github.io/llms-full.txt ## GitHub https://github.com/flowmcp ``` ## Available Files | File | Content | Size | |------|---------|------| | llms.txt (https://flowmcp.github.io/llms.txt) | Standard index — overview and links | ~25 lines | | docs-llms.txt (https://flowmcp.github.io/docs-llms.txt) | Practical docs — getting started, schemas, agents, CLI | ~8500 lines | | llms-full.txt (https://flowmcp.github.io/llms-full.txt) | Full website content embedded | ~9500 lines | | Spec llms.txt (https://raw.githubusercontent.com/FlowMCP/flowmcp-spec/refs/heads/main/spec/v3.0.0/llms.txt) | Formal v3.0.0 specification (auto-generated from spec repo) | ~9200 lines | ## What is llms.txt? The llms.txt standard (https://github.com/answerdotai/llms-txt) (Jeremy Howard, answer.ai) defines a compact, machine-readable format for project documentation. It allows AI agents to load all relevant context in one step instead of crawling individual pages. --- # Schemas and Tools /basics/schemas-and-tools ## What is a Schema? A schema is the complete blueprint for accessing a data source. It describes a single data provider — for example the German Weather Service, Deutsche Bahn, or a bike-sharing system like nextbike. **One schema per data provider. Multiple tools per schema.** The schema does not change the data source itself. It translates between the provider's API and the AI — so the AI can query the data in a structured way without having to read and interpret the API documentation itself. ## What's Inside a Schema? A schema contains four sections. Only Tools are required — the other three are optional and come into play with more complex data sources. ```mermaid graph TD P[Provider Schema] --> Tools[Tools - Required] P --> Resources[Resources - Optional] P --> Prompts[Prompts - Optional] P --> Skills[Skills - Optional] Tools --> T1[method, path, parameters] Tools --> T2[modifiers, tests, output] Resources --> R1[Local data: SQLite, Markdown] Prompts --> P1[Model-neutral AI guidance] Skills --> S1[Multi-tool step-by-step workflows] ``` ### Tools (Required) Tools are the core building blocks. Each tool is a single query to the data provider's API. A schema can contain multiple tools — typically between 2 and 8. Each tool defines: | Component | What it does | |-----------|-------------| | **method** | HTTP method (GET, POST) | | **path** | The API endpoint (e.g., `/weather/:city`) | | **parameters** | What goes in — with validation (type, required, limits) | | **modifiers** | Pre- and post-processing of data | | **tests** | At least 3 test cases per tool — ensuring it works | | **output** | What comes back — as a structured schema so the AI understands the response | ### Resources (Optional) Local data that doesn't come from an API but is delivered directly with the schema. For example, a SQLite database with reference data or a Markdown file with explanations. ### Prompts (Optional) Guidance for the AI on how to best use this provider's tools. Formulated model-neutrally — they help any AI, not just a specific one. ### Skills (Optional) Step-by-step instructions for complex workflows combining multiple tools. For example: "First find the station, then query connections, then compare prices." ## Example: Bright Sky (German Weather Service) The Bright Sky schema makes DWD weather data accessible. It contains two tools: - **getWeather** — Current weather for a location (Parameters: latitude, longitude, date → Result: temperature, precipitation, wind, cloud cover) - **getForecast** — 7-day forecast for a location (Parameters: latitude, longitude → Result: forecast per day) ``` Data source: https://api.brightsky.dev Tool: getWeather Call: GET /weather?lat=52.52&lon=13.41&date=2026-03-28 Result: { temperature: 14.2, precipitation: 0, wind_speed: 12.5 } ``` The AI sees this tool and knows: "I can query weather with this." It doesn't need to know the DWD's API documentation — the schema has already done that work. ## How Does a Schema Become Usable? A schema starts as a `.mjs` file — a description in code. For the tools it contains to become available to AI clients, a server needs to provide them. That's what FlowMCP does — an open-source framework that loads schemas, validates the structure, runs the tests, and exposes the tools via the **Model Context Protocol (MCP)**. Over 100 AI clients already support MCP — from Claude to ChatGPT to Cursor. The result: a tool defined in a schema can be called by any MCP-compatible AI. Described once, usable everywhere. ## How Are Schemas Created? Schemas are created and maintained by the community and the FlowMCP team — based on publicly accessible API documentation and in collaboration with data partners. Each schema is tested (at least 3 tests per tool), validated, and documented before it becomes available. The community can contribute schemas through a 5-stage pipeline with automatic validation, AI review, and human approval. The principle: **"Validated once, for all."** What has been carefully reviewed once is available to everyone afterward. ## Create Your Own Schemas Schemas follow the FlowMCP Specification v3.0.0: - **Documentation:** Schema Overview - **Specification:** FlowMCP Spec v3.0.0 (https://github.com/FlowMCP/flowmcp-spec) - **How to contribute:** Community Hub → - **Schema repository:** github.com/flowmcp/flowmcp-schemas-public (https://github.com/flowmcp/flowmcp-schemas-public) --- # Agents and Architectures /basics/agents ## How Tools Are Selected From the schema catalog, individual tools are selected and assembled into a **Tool Set**. Not all tools are needed — only those relevant to the specific use case. ```mermaid graph LR Cat[Schema Catalog] --> S1[Schema A: Weather] Cat --> S2[Schema B: Transit] Cat --> S3[Schema C: Bikes] S1 --> T1[getWeather] S1 --> T2[getForecast] S2 --> T3[getConnections] S3 --> T4[findStations] T1 --> TS[Tool Set] T3 --> TS T4 --> TS ``` ## What is an Agent? ```mermaid graph TD Agent[Agent Manifest] Agent --> LLM[Own LLM e.g. Claude Haiku] Agent --> SP[System Prompt: domain expertise] Agent --> SK[Skills: multi-step workflows] Agent --> TS[Tool Set: selected tools] TS --> Loop[Agentic Loop] Loop --> Understand[1. Understand question] Loop --> Select[2. Select tool] Loop --> Call[3. Call tool] Loop --> Evaluate[4. Evaluate result] Loop --> Decide[5. Done or loop again] ``` An agent is more than a simple tool query. While a tool asks a single question to a data source ("What's the weather in Berlin?"), an agent can **combine multiple tools, reason, and independently decide** what information it still needs. The key difference: an agent has its **own language model (LLM)** that thinks for it. It's not just a program executing commands, but an expert that understands questions, selects tools, and formulates answers. An agent consists of: - **Its own LLM** — the language model the agent uses to think and decide - **A system prompt** — defines behavior: "You are a mobility expert. Answer questions about connections, weather, and sharing options." - **Tool references** — the agent selects tools from various provider schemas. It doesn't take all of them, only the relevant ones. - **Tests** — verify that the combination of LLM, prompt, and tools actually works **Example:** A Mobility Agent combines tools from the DB schedule schema (getConnections), the OpenWeather schema (getWeather), and the nextbike schema (findStations). It uses Claude Haiku as its LLM and knows from its system prompt that it should answer mobility questions. ## The Agentic Loop An agent works not linearly, but in **loops**. This is the central difference from a simple tool call: 1. **Understand the question** — What does the user want to know? 2. **Select a tool** — What data do I need? 3. **Call the tool** — Query the data 4. **Evaluate the result** — Is this enough? Do I have everything? 5. **Decide** — Done → formulate answer. Or: need another tool → back to step 2. The loop runs until the answer is complete — or a configured maximum of iterations is reached. **Why this matters:** When someone asks "Should I bike or take the S-Bahn tomorrow?", the agent must: - First check the weather (Tool 1) - Then query S-Bahn connections (Tool 2) - Then check if there are bike stations nearby (Tool 3) - Only then formulate a recommendation A simple tool call could only do one of these steps. The agentic loop does all of them — and thinks in between. **The tradeoff:** The agent needs its own LLM. That costs compute. But the answer quality is significantly better than individual tool calls. ## Three Usage Architectures Not every request needs a full agent. There are three levels — from simple to complex: ```mermaid graph TD subgraph Level 1: Tools Only U1[User AI] -->|direct call| T1[Tool] end subgraph Level 2: Sub-Agent U2[User AI] --> A2[Agent with own LLM] A2 -->|agentic loop| T2a[Tool A] A2 -->|agentic loop| T2b[Tool B] end subgraph Level 3: Orchestration U3[User AI] --> C3[Coordinator Agent] C3 --> SA1[Sub-Agent 1] C3 --> SA2[Sub-Agent 2] SA1 --> T3a[Tool A] SA1 --> T3b[Tool B] SA2 --> T3c[Tool C] SA2 --> T3d[Tool D] end ``` ### Level 1: Tools Only The simplest level. The user's AI calls individual tools directly — no agent, no loops, no additional LLM. - **How it works:** The user asks "What's the weather in Berlin?", the AI recognizes the right tool and calls it - **Result:** Raw data that the user's AI interprets itself - **Advantage:** Fast, cheap, works with **108+ MCP clients** - **Disadvantage:** No combining multiple data sources, no loops ### Level 2: Sub-Agent A specialized agent with its own LLM. It has its own tools, its own logic, and can use the agentic loop. - **How it works:** The request goes to an expert agent that independently selects and combines the right tools - **Result:** An interpreted, prepared answer — not just raw data - **Advantage:** Better answer quality, combining multiple data sources - **Disadvantage:** Needs an additional LLM, costs more compute ### Level 3: Orchestration The most complex level. A **coordinator agent** distributes requests to multiple **sub-agents**. Each sub-agent is an expert in its domain. - **How it works:** The coordinator understands the question, decides which experts need to be consulted, collects their answers, and formulates an overall response - **Result:** A combined, optimized answer from multiple domains - **Advantage:** Complex questions can be answered that no single agent could handle alone - **Disadvantage:** Multiple LLMs run in parallel — highest compute cost ## Elicitation: When the Agent Asks Back Not every question can be answered immediately. "How do I get to the train station?" — Which station? From where? On foot or by bus? **Elicitation** allows the agent to ask **structured follow-up questions** before answering. This is a feature of the MCP protocol and works from Level 2 onward. Examples of follow-up questions: - "What location do you want to start from?" - "Do you mean today or tomorrow?" - "Should I also consider sharing options?" - "Do you mean Berlin Hauptbahnhof or Berlin Suedkreuz?" **Why this matters so much:** Without elicitation, the agent has to guess or work with incomplete information. With elicitation, it asks — and the answer becomes significantly better. The difference is often between a useful and a useless response. **Not all clients support elicitation.** Currently, 16 MCP clients do — including Claude Desktop, OpenClaw, and Codex. Clients without elicitation still work with Level 2 and 3 — the agent just can't ask follow-up questions and works with what it has. Which clients support what: Clients and Compatibility → ### Example: Level 3 in Practice A typical Level 3 implementation uses a coordinator agent that routes requests to specialized sub-agents. Each sub-agent has access to specific schemas and tools. For example, a travel planning system might use: - A **Schedule Agent** (train connections, flight data) - A **Weather Agent** (forecasts, alerts) - A **Location Agent** (geocoding, points of interest) The coordinator decides which agents to involve based on the user's question. This pattern works across any domain where multiple data sources need to be combined. ## Learn More - **Agent manifests and configuration:** Agents Overview - **FlowMCP Specification:** FlowMCP Spec v3.0.0 (https://github.com/FlowMCP/flowmcp-spec) - **MCP Clients:** modelcontextprotocol.io/clients (https://modelcontextprotocol.io/clients) --- # Clients and Compatibility /basics/clients ## MCP — The Connecting Protocol The **Model Context Protocol (MCP)** is the standard through which AI clients access tools. It defines how tools are described, called, and how results are returned. Over 100 clients support MCP already — from Claude to ChatGPT to Cursor. Our schemas are based on MCP and work with any compatible client. You are not locked into any specific provider. ```mermaid graph LR MCP[MCP Protocol] MCP --> C1[Claude Desktop] MCP --> C2[ChatGPT] MCP --> C3[Cursor] MCP --> C4[VS Code Copilot] MCP --> C5[OpenClaw] MCP --> C6[108+ more clients] style MCP fill:#f9f,stroke:#333 ``` ## Compatibility Table Not every client can do everything. Capabilities depend on which MCP features the client supports: | Level | What is supported | Number of Clients | Examples | |-------|------------------|------------------|----------| | **Level 1: Tools** | Individual tool calls, Resources, Prompts | 46+ clients | Claude, ChatGPT, Cursor, VS Code Copilot, Cline, Continue | | **Level 2+3: Elicitation** | Everything from Level 1 + Agent can ask follow-up questions | 16 clients | Claude Code, Claude Desktop, OpenClaw, Codex, Cursor, Postman | | **Custom CLI** | Command-line interfaces for direct access | FlowMCP CLI, OpenClaw Plugin | For developers and automation | What the levels mean: Agents and Architectures → **As of:** March 2026. Current list: modelcontextprotocol.io/clients (https://modelcontextprotocol.io/clients) ## Clients with Elicitation (Level 2+3) These 16 clients support Elicitation — the agent can ask follow-up questions for better answers: 1. AIQL TUUI 2. Claude Code 3. Codex 4. Cursor 5. fast-agent 6. Glama 7. goose 8. Joey 9. mcp-agent 10. mcp-use 11. MCPJam 12. Memgraph Lab 13. Postman 14. Tambo 15. VS Code GitHub Copilot 16. VT Code Why elicitation matters: Agents and Architectures → Elicitation ## CLI — Command-Line Interfaces Besides graphical clients, there are command-line interfaces that are particularly relevant for developers and automation. ### FlowMCP CLI The fastest way for developers to find schemas and try tools: - `flowmcp list` — Show all available schemas - `flowmcp search ` — Search schemas by keyword - `flowmcp add ` — Activate a schema - `flowmcp call '{...}'` — Call a tool directly Documentation: CLI Usage ### OpenClaw OpenClaw (https://docs.openclaw.ai) is an open-source AI assistant gateway with a plugin system. The special feature: **Cron Jobs** — recurring queries that run automatically. For example, a daily mobility recommendation every morning at 7:30 AM. More: Integration → ## Which Client for Whom? | You are... | Recommended Client | Why | |-----------|-------------------|-----| | **Individual** | OpenClaw (WhatsApp, Telegram, Slack) or Claude Desktop | Where you already are, Cron Jobs for automation | | **Developer** | Claude Code, Cursor, or FlowMCP CLI | Direct control, fast testing, IDE integration | | **Enterprise** | NemoClaw (enterprise security) | Deny-by-default policies, sandbox isolation, audit trail | Details on enterprise integration: Integration → NemoClaw --- # Roadmap /roadmap/overview ## Public Schemas ✅ **Status: Available** FlowMCP schemas are publicly available — curated, validated, and open source. Currently covering transportation, weather, geocoding, and infrastructure data. - [x] **14 public schemas** across 9 data providers - [x] **Automated schema catalog** with JSON generation - [x] **Schema validation** and test infrastructure - [ ] Expand to additional domains (environment, administration, health) Schema Catalog → --- ## Integration 🔄 **Status: In Progress** Integration into existing MCP clients — not standalone, but embedded in platforms that developers already use. - [ ] CLI-first approach with FlowMCP CLI - [ ] OpenClaw integration - [ ] Documentation and getting-started guides Details → --- ## Community Hub ⏳ **Status: Planned** A community-driven schema ecosystem where anyone can contribute, review, and validate schemas through a structured pipeline. - [ ] 5-stage contribution pipeline - [ ] Automated validation and scoring - [ ] Community governance process Details → --- ## Get Involved - **Use schemas** — Schema Catalog → - **Contribute schemas** — Community Hub → - **Follow development** — GitHub (https://github.com/flowmcp) --- # Community Hub (Coming Soon) /roadmap/community :::caution[In Planning] The Community Hub describes our vision for the next phase. Technical implementation has not started yet — but the architecture and processes are thought through and documented. ::: ## The Idea Public data belongs to everyone. But preparing this data for AI systems is expensive. When every AI does this work on its own — reading API documentation, guessing endpoints, figuring out parameters — enormous compute is wasted. And results are inconsistent because every AI takes a different path. Our solution: **A schema is created once, validated once, and can then be used by every AI.** This saves energy, creates consistency, and makes data reliably accessible. We call this principle **"Validated once, for all"**. The Community Hub is where this principle becomes reality. It is not a central service or a backend — it is a **GitHub repository** that grows through community processes. ## GitHub as Primary Platform We deliberately use GitHub as the platform for the hub: - **Schemas without API keys need no backend.** No secrets, no server security. Everything can be public. - **GitHub provides everything we need:** Issues for submissions, Pull Requests for reviews, Actions for automated tests, Discussions for questions. - **Traceability:** Every change is versioned and transparent. Who contributed what and when is permanently verifiable. - **No backend** means: less maintenance, less attack surface, lower cost. **Honestly:** GitHub is a company, and not everyone has or wants an account. That is a bottleneck. Long-term, we are thinking about a minimal intermediary service that enables participation without a GitHub profile. But for the start, GitHub is the best solution with the least overhead — and for an open-source project, a GitHub account is reasonable. ### Schemas With and Without API Keys | Type | In Hub? | Note | |------|---------|------| | **Without API key** | Yes, fully | Can run completely via GitHub — no backend needed | | **With API key** | Yes, but separate scope | User must register with the provider themselves. Clearly marked to avoid surprises | ## Two Ways to Use the Project Not everyone needs to contribute. Most users will simply use schemas — and that is perfectly fine. ### 1. Use Schemas (Default) Load validated schemas via OpenClaw or CLI and query data. No GitHub account needed, no contribution expected. The schemas are there, they work, you use them. ### 2. Contribute Schemas (Opt-in) You can additionally create and submit schemas yourself. Maybe you work at an organization that provides public data. Maybe you have a Swagger file and an AI that can turn it into a schema. The path: your AI creates the schema according to our spec, you submit it as an issue, the community reviews it. **The special part:** The compute for schema creation comes from the user — their AI does the work. The spec — the rules for how a schema must look — comes from FlowMCP. Without correct spec compliance, nothing gets accepted. But when it passes, the entire community benefits. This is real crowdsourcing: not just giving feedback, but making productive contributions. Every user with an AI is theoretically also a developer. ## The Feedback Loop ```mermaid graph TD AI[AI Agent discovers data source] --> Draft[AI creates schema draft] Draft --> Submit[Submit as GitHub Issue] Submit --> Review[Community reviews] Review --> Accept[Schema accepted into Hub] Accept --> Use[Everyone can use schema] Use --> Discover[More data sources discovered] Discover --> AI ``` The vision is a self-reinforcing cycle: an AI discovers a new data source, creates a schema draft, submits it as an issue, the community reviews — and if accepted, the schema is available to everyone. Through usage, more data sources are discovered, and the cycle begins again. ## The Schema Pipeline: 5 Stages Every new schema goes through the same pipeline. No exceptions — not even for us as the project team. This process ensures that only quality-checked schemas end up in the hub. ```mermaid graph LR S1[Stage 1: Submission] --> S2[Stage 2: Automatic Validation] S2 --> S3[Stage 3: AI Review] S3 --> S4[Stage 4: Human Approval] S4 --> S5[Stage 5: Integration] S2 -->|fail| Reject[Rejected with feedback] S3 -->|fail| Reject S4 -->|fail| Reject ``` ### Stage 1: Submission A user creates an **Issue** in the schema repository with the tag `NewSchema`. The issue follows a fixed template — machine- and human-readable: - **Top:** Provider description, available routes, license information - **Middle:** Arguments from the submitting AI — why this schema, license proof, plausibility assessment - **Bottom:** The actual schema code as a Markdown code block The entire process is designed so that unknown AI systems can make understandable submissions. ### Stage 2: Automatic Validation A GitHub Action is triggered by the `NewSchema` tag and checks deterministically: - Is the schema parseable and FlowMCP spec compliant? - Do described routes match the actual schema? - Do runtime tests pass? (real API calls) - Does the output match the defined output schema? - Does each route have at least 3 tests? **If anything fails here, the schema is immediately rejected** — with automatic feedback directly in the issue. No human effort for obviously flawed submissions. ### Stage 3: AI Review and Plausibility What automated tests cannot catch, an AI reviews: - **Legal concerns:** Is the license clear and verifiable? Just because someone writes "MIT License" does not mean the data is actually freely available. Similar to consumer protection law: if something sounds too good to be true, there is a duty to verify. - **Moral concerns:** Are there problematic contents? - **Quality:** Are descriptions meaningful? Naming conventions followed? Do returned data match the description? The submitting AI can have arguments prepared — for example: "The data are official government publications and therefore in the public domain. Here is the link to the original source." This review follows a **scoring system** (see below). ### Stage 4: Human Approval A maintainer gives final approval. The entire process so far — automated tests, AI evaluation, arguments — is documented in the issue history. The human checks what neither tests nor AI can reliably judge: Does this schema fit the project? Is the data source trustworthy? **The human has the final word — always.** ### Stage 5: Integration The schema is merged. The issue ID is referenced in the commit so that the origin of every schema is traceable. The registry is automatically updated. From this moment on, every user can use the schema. ## Quality Standards and Scoring ```mermaid graph TD Score[Schema Quality Score] Score --> C1[Legality: 0-5 stars] Score --> C2[Quality: 0-5 stars] Score --> C3[Test Coverage: 0-5 stars] Score --> C4[Usefulness: 0-5 stars] Score --> C5[Documentation: 0-5 stars] C1 --> Avg[Average >= 3.0 stars required] C2 --> Avg C3 --> Avg C4 --> Avg C5 --> Avg Avg --> Pass[Pass: schema accepted] Avg --> Fail[Fail: improve and resubmit] ``` Each schema is evaluated in Stage 3 across five criteria, each rated 0 to 5 stars: | Criterion | What is evaluated | |-----------|------------------| | **Legality** | License clear and plausible? Data origin verifiable? | | **Quality** | Descriptions understandable? Naming conventions followed? | | **Test Coverage** | Enough tests? Edge cases considered? | | **Usefulness** | Relevant data source? Reliable? Demand exists? | | **Documentation** | Issue thorough? Arguments traceable? Sources linked? | **Thresholds:** - The average across all five criteria must reach at least **3.0 stars** - **No single criterion may have 0 stars** — if any area completely fails, the schema is rejected - Borderline cases allow the submitter to improve and resubmit This scoring system is a first draft. Criteria and thresholds will be iteratively refined as real submissions come in. What matters: the process is traceable, transparent, and documented in the issue history. ## Open Questions — Honestly Named We know this approach brings challenges for which there is no perfect solution yet: - **Not everyone has GitHub:** About 90% of OpenClaw users do not have a GitHub profile today. Long-term, we plan a minimal intermediary service so that users without GitHub can also contribute. For the start, we use GitHub Issue Templates as the lowest barrier. - **Fraud and quality:** When any AI can submit schemas, flawed or malicious submissions will come in. That is why we have the 5-stage pipeline: automated tests catch the obvious, AI review uncovers subtleties, and a human decides in the end. - **Scaling:** How many submissions can a small team review? That depends on how well automated stages 2 and 3 work. The better the automation, the more the community can grow. We communicate these open points deliberately and transparently. A project that claims to have all the answers is not credible. One that asks the right questions and works on solutions is. ## Get Involved There are several ways to contribute to the project: - **Become a data partner:** You have access to public data and want to make it accessible for AI systems? Meet the team → - **Contribute schemas:** You have an AI and a data source? Create a schema following the FlowMCP Spec and submit it as an issue. - **Give feedback:** Something is not working? A data source is missing? Issues on GitHub (https://github.com/flowmcp/flowmcp-schemas-public/issues) are the right place. Schema repository: github.com/flowmcp/flowmcp-schemas-public (https://github.com/flowmcp/flowmcp-schemas-public) --- # Integration (Coming Soon) /roadmap/integration :::caution[In Planning] This page describes the planned next phase. The architecture is designed, technical implementation has not started yet. ::: ## From Demo to Real Usage FlowMCP schemas work end-to-end — from data source to AI agent response. But a standalone app is not the goal. Most people do not want to install yet another app. They want answers where they already are: in WhatsApp, Telegram, Slack, or their preferred AI assistant. That is exactly what this phase is about: integrating our schemas and agents into **existing clients** — not as an island, but as a building block in a larger ecosystem. ## Why CLI-first? When we designed the first architecture in October 2025, the plan was: everything via MCP servers. The Model Context Protocol was the standard, and it worked. But since then, something has changed. In practice, AI agents work most reliably with **command-line interfaces**. The reasons are technical: session initialization with MCP is error-prone, state management across protocol boundaries is complicated, and instant updates work better with CLI. **This does not mean MCP is dead.** MCP remains as a second channel — for clients that prefer it. FlowMCP supports both. But for the primary integration path, we choose CLI because it works most reliably today. ## OpenClaw: Why This Project? OpenClaw (https://docs.openclaw.ai) is an open-source AI assistant gateway under MIT license. It has existed only since November 2025 and has already reached over 330,000 GitHub stars — more than Linux, Kubernetes, and Blender combined. It is the fastest-growing software project on GitHub. For us, OpenClaw is the right partner for several reasons: **Cron jobs change everything.** Most AI interactions are reactive: the user asks, the AI responds. With OpenClaw, we can set up **proactive** queries. A cron job runs automatically — for example every morning at 7:30 AM — and delivers results without the user doing anything. This is not possible with pure MCP servers or MCPUI. **Multi-channel, not multi-app.** OpenClaw delivers answers via WhatsApp, Telegram, Slack, Discord, and more. The user decides where to receive the data — not us. Our schemas work identically in every channel. **No gatekeeper.** On other platforms (e.g., OpenAI MCPUI), you need approval to be visible. Not with OpenClaw. Open source means: anyone can install and use our plugin — immediately, without approval. ## Three Integration Levels Integration is not a single step but a staged model. Each level brings our schemas closer to users: | Level | What happens | For whom | Status | |-------|-------------|----------|--------| | **Level 1: MCP Server** | Our schemas are embedded directly as an MCP server in OpenClaw. All three usage architectures work: individual tools, sub-agents with their own intelligence, or full orchestration with a coordinator. | Developers using MCP clients | Possible | | **Level 2: OpenClaw Plugin** | An npm package that registers each schema as a tool. Publishable on ClawHub, installable with one command. The fastest path for end users. | All OpenClaw users | Planned | | **Level 3: NemoClaw Policy Preset** | A YAML file bundling all API endpoints of our schemas. Enterprise customers can unlock instantly — with the security policies their organization requires. | Companies and government agencies | Planned | ### Enterprise Security with NemoClaw For use in companies and government agencies, open source alone is not enough. You need security policies, sandbox isolation, and controlled release processes. NVIDIA NemoClaw (https://docs.nvidia.com/nemoclaw/) is the enterprise security layer for OpenClaw (Apache 2.0, Alpha since March 2026). It offers deny-by-default network policies, sandbox isolation, and a blueprint system. A policy preset would bundle all API endpoints of our schemas — so a security officer could enable access to all open data sources with a single approval. This matters because: public data is public, but access to it within an organization still needs to be regulated. NemoClaw makes this possible without us having to run enterprise infrastructure ourselves. ## Open and Free: Local Operation Not everyone wants or can use cloud services. That is why we are working in parallel on a fully local solution: - **llama.cpp** as a local LLM — no API key, no costs, full control over your own data - Running on a **Raspberry Pi** — a device for under 100 euros, completely independent from cloud services - Ideal for **individuals** who do not want to send their data to third parties, for **schools** working on limited budgets, and for **organizations** with strict data privacy requirements Our schemas work locally just as they do in the cloud. That is the principle of open protocols: data and preparation are separate from the operating model. If you want to work locally, you can — without limitations. ## Example: Multi-Source Data Integration A practical integration combines multiple FlowMCP schemas through a single MCP server. For instance, a travel planning system could load schedule, weather, and location schemas — giving any connected AI client access to all three data sources through one endpoint. The same pattern works for any domain: environmental monitoring, public administration, financial data, or any combination of structured data sources. ## Pilot Program In parallel with technical integration, we are looking for **data partners** who want to co-develop AI connections for public data. We are not looking for money or labor — we are looking for data sources and the willingness to review a finished connection. **Meet the team →** ## Next Steps This phase is in preparation. Specifically, we are working on: 1. **Validation** of existing schemas with real data partners — do they work in everyday use? 2. **Optimization** based on real-world usage — better answer quality, better error handling 3. **OpenClaw integration** — the plugin that makes schemas available as tools 4. **Local operation** — testing with llama.cpp on Raspberry Pi More on the timeline: Roadmap --- # Team /roadmap/team The FlowMCP project is maintained as open source (MIT License) on GitHub (https://github.com/flowmcp). *More details coming soon.* --- # Agents Overview /docs/agents/overview Agents are purpose-driven compositions that bundle tools from multiple providers into a single, testable unit. Where individual schemas wrap a single API, agents combine the right tools for a specific task -- for example, a crypto research agent might pull from CoinGecko, Etherscan, and DeFi Llama simultaneously. :::note This page covers the practical guide for building agents. For the full specification and validation rules, see Agents Specification. ::: ## Overview An agent manifest (`agent.mjs`) declares everything the agent needs: which tools to use, which model to target, how the agent should behave, and how to verify it works. ## Agent Manifest Each agent is defined by an `agent.mjs` file with `export const agent`: ```javascript export const agent = { name: 'crypto-research', version: 'flowmcp/3.0.0', description: 'Cross-provider crypto analysis agent', model: 'anthropic/claude-sonnet-4-5-20250929', systemPrompt: 'You are a crypto research agent...', tools: { 'coingecko-com/tool/simplePrice': null, 'coingecko-com/tool/coinMarkets': null, 'etherscan-io/tool/getContractAbi': null, 'defillama-com/tool/getProtocolTvl': null }, prompts: { 'research-guide': { file: './prompts/research-guide.mjs' } }, skills: { 'token-analysis': { file: './skills/token-analysis.mjs' } }, resources: {}, tests: [ { _description: 'Token price lookup', input: 'What is the current price of Bitcoin?', expectedTools: ['coingecko-com/tool/simplePrice'], expectedContent: ['bitcoin', 'price', 'USD'] }, { _description: 'Contract analysis', input: 'Analyze the USDC contract on Ethereum', expectedTools: ['etherscan-io/tool/getContractAbi'], expectedContent: ['USDC', 'contract'] }, { _description: 'DeFi protocol TVL', input: 'What is the TVL of Aave?', expectedTools: ['defillama-com/tool/getProtocolTvl'], expectedContent: ['Aave', 'TVL'] } ], sharedLists: ['evmChains'] } ``` ## Slash Rule Tools, prompts, and resources use a uniform convention: keys containing `/` are external references (value `null`), keys without `/` are inline definitions. | Key Pattern | Value | Meaning | |-------------|-------|---------| | Contains `/` | `null` | External reference from a provider schema | | No `/` | object | Inline definition owned by the agent | ```javascript tools: { 'coingecko-com/tool/simplePrice': null, // external 'customEndpoint': { method: 'GET', ... } // inline } ``` Skills are the exception -- they cannot have slash keys because they are model-specific and cannot be shared across different LLMs. ## Three Content Layers Agents separate concerns into three distinct layers that shape how the agent thinks, understands, and acts. :::note[Persona] **Field:** `systemPrompt` Who the agent IS. Defines personality, expertise, and behavioral boundaries. *"You are a crypto research analyst who provides data-driven insights..."* ::: :::note[Explanations] **Field:** `prompts` How tools work. Provides context about data formats, API quirks, and interpretation guidance. *"CoinGecko returns prices in the base currency. Always convert to USD for comparison..."* ::: :::note[Instructions] **Field:** `skills` Step-by-step workflows. Guides the agent through multi-tool sequences for complex tasks. *"Step 1: Search token by name. Step 2: Fetch OHLCV data. Step 3: Calculate metrics..."* ::: | Layer | Field | Purpose | Example | |-------|-------|---------|---------| | Persona | `systemPrompt` | Who the agent IS | "You are a crypto research analyst..." | | Explanations | `prompts` | How tools work | "CoinGecko returns prices in..." | | Instructions | `skills` | Step-by-step workflows | "Step 1: Search token. Step 2: Fetch OHLCV..." | ## Tool Cherry-Picking Tools are declared as object keys with the full ID format: `namespace/type/name`. External tools have `null` as value. This lets you pick exactly the tools an agent needs from any provider. ``` coingecko-com/tool/simplePrice # Key in tools object, value: null coingecko-com/tool/coinMarkets # Another tool from the same provider etherscan-io/tool/getContractAbi # Tool from a different provider defillama-com/tool/getProtocolTvl # Yet another provider ``` Select only the tools that serve the agent's purpose. A crypto research agent does not need every CoinGecko endpoint -- just `simplePrice` and `coinMarkets` might be enough. ## Agent Tests :::note Every agent must have a minimum of 3 tests. This ensures the agent's tool selection and output quality are verified across different usage scenarios. ::: Tests validate agent behavior at three levels: | Level | Field | What it checks | Deterministic? | |-------|-------|-----------------|----------------| | **Tool Usage** | `expectedTools` | Did the agent call the right tools? | Yes | | **Content** | `expectedContent` | Does the output contain expected keywords? | Partial | | **Quality** | Manual review | Is the output coherent and useful? | No | Each test case defines an input prompt, the tools the agent should reach for, and keywords the response should contain: ```javascript { _description: 'Token price lookup', input: 'What is the current price of Bitcoin?', expectedTools: ['coingecko-com/tool/simplePrice'], expectedContent: ['bitcoin', 'price', 'USD'] } ``` - **`expectedTools`** is deterministic -- the agent must call exactly these tools for the given input. - **`expectedContent`** is a partial check -- the response should contain these strings, but additional content is fine. - **Quality review** is manual -- read the output and verify it makes sense as a coherent answer. ## Directory Structure Each agent lives in its own directory within the catalog's `agents/` folder: ``` agents/crypto-research/ ├── agent.mjs # Manifest (export const agent) ├── prompts/ │ └── research-guide.mjs ├── skills/ │ └── token-analysis.mjs └── resources/ # Optional own databases ``` The `agent.mjs` file is the entry point. Prompts and skills are referenced by relative path from the manifest and follow the same format as schema-level skills and prompt architecture. --- # MCP Agent Server /docs/ecosystem/agent-server The MCP Agent Server (https://github.com/FlowMCP/mcp-agent-server) is an MCP server where each tool is backed by an AI agent loop. When an AI client calls a tool, the server starts an LLM agent that iteratively calls FlowMCP schema tools to solve the problem and returns a structured answer. ## Architecture ``` AI Client --> MCP Protocol --> AgentToolsServer | ToolRegistry | AgentLoop / \ LLM (OpenRouter) ToolClient | FlowMCP Schemas | External APIs ``` The AI client sends a request to the MCP server. The server starts an agent loop that uses an LLM to decide which FlowMCP tools to call, calls them, and iterates until the problem is solved. The final answer is returned to the AI client. ## Installation ```bash npm install mcp-agent-server ``` Or clone and run locally: ```bash git clone https://github.com/FlowMCP/mcp-agent-server.git cd mcp-agent-server npm install ``` ## Quickstart The server mounts as Express middleware: ```javascript import express from 'express' import { AgentToolsServer } from 'mcp-agent-server' const app = express() app.use( express.json() ) const { mcp } = await AgentToolsServer.create( { name: 'My Agent Server', version: '1.0.0', routePath: '/mcp', llm: { baseURL: 'https://openrouter.ai/api', apiKey: process.env.OPENROUTER_API_KEY }, tools: [ { name: 'defi-research', description: 'Research DeFi protocols and TVL data', inputSchema: { type: 'object', properties: { query: { type: 'string', description: 'Research query' } }, required: [ 'query' ] }, agent: { systemPrompt: 'You are a DeFi research agent. Use the available tools to answer questions about DeFi protocols, TVL, and market data.', model: 'anthropic/claude-sonnet-4-5-20250929', maxRounds: 10, maxTokens: 4096 }, toolSources: [ { type: 'flowmcp', schemas: [ defilamaSchema, coingeckoSchema ], serverParams: { DEFILAMA_KEY: process.env.DEFILAMA_KEY } } ] } ] } ) app.use( mcp.middleware() ) app.listen( 4100 ) ``` ## Key Methods ### `AgentToolsServer.create()` Creates a new MCP server instance from configuration. ```javascript const { mcp } = await AgentToolsServer.create( { name: 'My Server', version: '1.0.0', routePath: '/mcp', llm: { baseURL, apiKey }, tools: [ /* tool configs */ ], tasks: { store: customTaskStore } // optional } ) ``` | Key | Type | Description | Required | |-----|------|-------------|----------| | `name` | string | Server name for MCP handshake | Yes | | `version` | string | Server version | Yes | | `routePath` | string | Express route path (default `'/mcp'`) | No | | `llm` | object | LLM config `{ baseURL, apiKey }` | Yes | | `tools` | array | Array of tool configurations | Yes | | `tasks` | object | Task store config (default `InMemoryTaskStore`) | No | ### `.middleware()` Returns an Express middleware that handles MCP protocol requests (POST/GET/DELETE) on the configured route path. ```javascript app.use( mcp.middleware() ) ``` ### `.listToolDefinitions()` Returns all registered tools in MCP ListTools format. ```javascript const { tools } = mcp.listToolDefinitions() // tools: [ { name, description, inputSchema, execution? } ] ``` ## Tool Configuration Each tool in the `tools` array defines an agent-powered MCP tool: | Key | Type | Description | Required | |-----|------|-------------|----------| | `name` | string | Tool name used in MCP protocol | Yes | | `description` | string | What this tool does | Yes | | `inputSchema` | object | JSON Schema for tool input | Yes | | `agent` | object | Agent configuration | Yes | | `toolSources` | array | Where the agent gets its tools from | Yes | | `execution` | object | `{ taskSupport: 'optional' \| 'required' }` | No | ### Agent Configuration | Key | Type | Description | Required | |-----|------|-------------|----------| | `systemPrompt` | string | System prompt for the LLM | Yes | | `model` | string | LLM model ID (e.g., `'anthropic/claude-sonnet-4-5-20250929'`) | Yes | | `maxRounds` | number | Maximum agent iterations (default `10`) | No | | `maxTokens` | number | Max completion tokens (default `4096`) | No | | `answerSchema` | object | Custom JSON Schema for the `submit_answer` tool | No | ### Tool Sources Each entry in `toolSources` defines where the agent gets its tools: | Key | Type | Description | Required | |-----|------|-------------|----------| | `type` | string | Source type (currently `'flowmcp'`) | Yes | | `schemas` | array | FlowMCP schema objects | Yes | | `serverParams` | object | API keys and environment variables | No | :::note FlowMCP schemas run in-process -- no external MCP server is needed. The agent calls them directly via the ToolClient. ::: ## FlowMCP Schema Integration Import FlowMCP schemas and pass them as tool sources: ```javascript import { main as defilamaMain } from './schemas/defillama.mjs' import { main as coingeckoMain } from './schemas/coingecko.mjs' const tools = [ { name: 'defi-analyst', description: 'Analyze DeFi protocols', inputSchema: { /* ... */ }, agent: { systemPrompt: 'You are a DeFi analyst.', model: 'anthropic/claude-sonnet-4-5-20250929', maxRounds: 10, maxTokens: 4096 }, toolSources: [ { type: 'flowmcp', schemas: [ defilamaMain, coingeckoMain ], serverParams: { COINGECKO_API_KEY: process.env.COINGECKO_API_KEY } } ] } ] ``` ## x402 Payment Composition Add payment gating with no code coupling -- pure Express middleware ordering: ```javascript import express from 'express' import { X402Middleware } from 'x402-mcp-middleware/v2' import { AgentToolsServer } from 'mcp-agent-server' const app = express() app.use( express.json() ) // 1. Payment gate (optional) const x402 = await X402Middleware.create( { /* config */ } ) app.use( x402.mcp() ) // 2. Agent MCP Server const { mcp } = await AgentToolsServer.create( { /* config */ } ) app.use( mcp.middleware() ) app.listen( 4100 ) ``` :::tip The x402 middleware intercepts requests before they reach the agent server. If payment is required, the client receives a 402 response with payment options. Once paid, the request proceeds to the agent. ::: ## Features - **StreamableHTTP transport** with session-based connections - **LLM Agent Loop** with iterative tool calling via Anthropic SDK - **FlowMCP schemas** as in-process tool sources (no external server) - **Configurable answer schema** per tool for structured outputs - **MCP Tasks API** for async tool execution - **x402 composition** for payment gating via middleware ordering - **Multiple tool sources** per tool via CompositeToolClient ## Links - **GitHub**: FlowMCP/mcp-agent-server (https://github.com/FlowMCP/mcp-agent-server) - **npm**: `npm install mcp-agent-server` --- # AgentProbe /docs/ecosystem/agentprobe AgentProbe (https://github.com/FlowMCP/mcp-agent-validator) is a web-based multi-protocol validator for AI agent endpoints. Enter a URL and get instant assessment across eight protocol layers. **Live demo:** agentprobe.xyz (https://agentprobe.xyz) ## What It Does - **Input:** A single agent endpoint URL - **Assessment:** Runs the `mcp-agent-assessment` pipeline with protocol-specific validators - **Output:** Unified verdicts per protocol layer plus raw assessment data One URL is enough. AgentProbe discovers which protocols the endpoint supports and validates each one independently. ## Protocol Layers AgentProbe assesses eight protocol layers: | Layer | What it checks | |-------|---------------| | **HTTP** | Connectivity, HTTPS, SSL validation, CORS, HTTP/2 detection | | **MCP** | Server discovery, tool/resource/prompt listing, capability detection | | **A2A / AP2** | Agent card validation, AP2 version and role detection via `capabilities.extensions` and `X-A2A-Extensions` header | | **x402** | Payment-required endpoint detection with scheme, network, and token analysis | | **OAuth** | Authorization server metadata discovery | | **MCP Apps** | UI resource detection for MCP applications | | **HTML** | Website detection, Content-Type, SSL status, HTTP/2 | | **ERC-8004** | On-chain agent registry lookup with OASF classification, reputation, and metadata extraction | :::note Not every endpoint supports all protocols. AgentProbe gracefully handles unsupported layers and shows results only for detected protocols. ::: ## Architecture ``` URL Input --> Server --> mcp-agent-assessment | +-------+-------+-------+-------+ | | | | | HTTP MCP A2A/AP2 x402 OAuth | | | | | +-------+-------+-------+-------+ | Results UI ``` The server receives a URL, passes it to the unified assessment pipeline, and returns structured results for each protocol layer. ## API Endpoints ### `POST /api/validate` Returns a structured validation result with separate sections for each protocol. ```bash curl -X POST https://agentprobe.xyz/api/validate \ -H 'Content-Type: application/json' \ -d '{"url": "https://your-endpoint.example.com/mcp"}' ``` The response contains `mcp`, `a2a`, `ui` (MCP Apps), and `oauth` objects, each with `status`, `categories`, `summary`, and `messages`. ### `POST /api/assess` Returns the raw assessment result from `mcp-agent-assessment` with full layer details. ```bash curl -X POST https://agentprobe.xyz/api/assess \ -H 'Content-Type: application/json' \ -d '{"url": "https://your-endpoint.example.com/mcp"}' ``` Optional parameters: | Key | Type | Description | |-----|------|-------------| | `timeout` | number | Timeout in milliseconds | | `erc8004` | object | ERC-8004 config with `rpcNodes` | ### `POST /api/lookup` Query the ERC-8004 on-chain registry for agent registration data, endpoints, OASF classification, and reputation. ```bash curl -X POST https://agentprobe.xyz/api/lookup \ -H 'Content-Type: application/json' \ -d '{"agentId": 2340, "chainId": 8453}' ``` | Key | Type | Description | Required | |-----|------|-------------|----------| | `agentId` | number | Agent token ID in the ERC-8004 registry | Yes | | `chainId` | number or string | Chain ID (e.g., `8453` for Base) or CAIP-2 (e.g., `eip155:8453`) | Yes | | `rpcNodes` | object | Custom RPC nodes per chain alias | No | ## Validate Your Own MCP Server 1. **Deploy your MCP server** Make sure your MCP server is publicly accessible (e.g., via ngrok or a cloud deployment). 2. **Open AgentProbe** Go to agentprobe.xyz (https://agentprobe.xyz) in your browser. 3. **Enter your endpoint URL** Paste your MCP server URL (e.g., `https://your-server.example.com/mcp`) and click validate. 4. **Review results** AgentProbe shows verdicts for each protocol layer. Green means supported and valid, red means issues detected, gray means not applicable. Or use the API directly: ```bash curl -X POST https://agentprobe.xyz/api/validate \ -H 'Content-Type: application/json' \ -d '{"url": "https://your-server.example.com/mcp"}' ``` ## Authentication Authentication is optional. When `API_TOKEN` is not set, the API is open (dev mode). When `API_TOKEN` is set, two authentication methods are supported: | Method | How it works | |--------|-------------| | **Session Cookie** | Browser visits the UI and receives a session cookie automatically | | **Bearer Token** | External scripts send `Authorization: Bearer ` header | ```bash # With Bearer token curl -X POST https://agentprobe.xyz/api/validate \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer your-token-here' \ -d '{"url": "https://your-endpoint.example.com"}' ``` ## Dependencies AgentProbe builds on the FlowMCP validator ecosystem: | Package | Purpose | |---------|---------| | mcp-agent-assessment (https://github.com/FlowMCP/mcp-server-assessment) | Unified assessment pipeline | | a2a-agent-validator (https://github.com/FlowMCP/a2a-agent-validator) | A2A agent card and AP2 detection | | x402-mcp-validator (https://github.com/FlowMCP/x402-mcp-validator) | x402 payment protocol validation | | mcp-apps-validator (https://github.com/FlowMCP/mcp-apps-validator) | MCP Apps UI resource detection | | erc8004-registry-parser (https://github.com/FlowMCP/erc8004-registry-parser) | ERC-8004 on-chain registry parsing | ## Links - **Live Demo**: agentprobe.xyz (https://agentprobe.xyz) - **GitHub**: FlowMCP/mcp-agent-validator (https://github.com/FlowMCP/mcp-agent-validator) - **Video**: Watch on YouTube (https://www.youtube.com/watch?v=gnmsCEly3fA) - **DoraHacks**: dorahacks.io/buidl/39293 (https://dorahacks.io/buidl/39293) --- # Schema Library /docs/ecosystem/schema-library The FlowMCP Schema Library (https://github.com/FlowMCP/flowmcp-schemas) is a curated collection of 187+ production-ready schemas covering DeFi, blockchain analytics, utilities, and more. Each schema follows the FlowMCP v2.0.0 specification and is validated, tested, and ready to use. **Browse schemas:** flowmcp.github.io/flowmcp-schemas (https://flowmcp.github.io/flowmcp-schemas/) ## Categories :::note[DeFi & Blockchain] **Etherscan** -- Contract ABI, balances, transactions, gas tracking across EVM chains **CoinGecko** -- Prices, market data, coin metadata, trending **DeFi Llama** -- Protocol TVL, chain analytics, yields, stablecoin data **Moralis** -- Token transfers, NFT data, wallet history **Dune Analytics** -- SQL query execution and result retrieval **CoinCap** -- Real-time asset pricing and exchange data ::: :::note[Blockchain Infrastructure] **Solscan** -- Solana account data, transactions, token info **Helius** -- Solana RPC and DAS API **CryptoCompare** -- Crypto pricing and historical data **DeBank** -- Portfolio tracking across chains **DexScreener** -- DEX pair analytics and trading data ::: ## How to Use 1. **Browse available schemas** Visit flowmcp.github.io/flowmcp-schemas (https://flowmcp.github.io/flowmcp-schemas/) to explore all available schemas, or use the CLI: ```bash flowmcp search etherscan flowmcp search "gas price" flowmcp search defi ``` 2. **Import schemas** Import the full schema library into your FlowMCP CLI: ```bash flowmcp import https://github.com/FlowMCP/flowmcp-schemas ``` Or if you already ran `flowmcp init`, schemas may already be imported. 3. **Add tools to your project** Activate specific tools: ```bash flowmcp add coingecko_simplePrice flowmcp add etherscan_getGasOracle ``` Or create a group with multiple tools: ```bash flowmcp group append defi --tools "flowmcp-schemas/coingecko/simplePrice.mjs,flowmcp-schemas/defillama/protocols.mjs" flowmcp group set-default defi ``` 4. **Call tools** Execute tools directly: ```bash flowmcp call coingecko_simplePrice '{"ids":"bitcoin","vs_currencies":"usd"}' ``` Or start an MCP server to expose them to AI clients: ```bash flowmcp run ``` ## Using Schemas Programmatically Import schemas directly in your Node.js code: ```javascript import { FlowMCP } from 'flowmcp-core' // Load a schema from file const { status, main, handlerMap } = await FlowMCP.loadSchema( { filePath: './node_modules/flowmcp-schemas/coingecko/simplePrice.mjs' } ) // Execute a tool call const result = await FlowMCP.fetch( { main, handlerMap, userParams: { ids: 'bitcoin', vs_currencies: 'usd' }, serverParams: {}, routeName: 'simplePrice' } ) console.log( result.dataAsString ) ``` ## API Keys Some schemas require API keys. These are declared in the schema's `requiredServerParams` field: | Provider | Required Key | Free Tier | |----------|-------------|-----------| | Etherscan | `ETHERSCAN_API_KEY` | Yes | | CoinGecko | `COINGECKO_API_KEY` | Yes (limited) | | Moralis | `MORALIS_API_KEY` | Yes | | Dune Analytics | `DUNE_API_KEY` | Yes (limited) | | Helius | `HELIUS_API_KEY` | Yes | Store API keys in `~/.flowmcp/.env`: ```bash ETHERSCAN_API_KEY=your_key_here COINGECKO_API_KEY=your_key_here MORALIS_API_KEY=your_key_here ``` :::note Schemas without `requiredServerParams` (like CoinGecko ping or DeFi Llama protocols) work without any API keys. ::: ## Shared Lists Many schemas reference shared lists for cross-provider value normalization. The most common shared list is `evmChains`, which provides a unified chain registry: ```javascript // Schema references the shared list sharedLists: [ { ref: 'evmChains', version: '1.0.0', filter: { key: 'etherscanAlias', exists: true } } ] // Parameter uses the list for enum generation z: { primitive: 'enum({{evmChains:etherscanAlias}})', options: [] } ``` This means a single schema can support multiple EVM chains through the same parameter, with the chain list maintained centrally. ## Contributing New schemas are welcome. Follow these steps: 1. **Fork the repository** Fork FlowMCP/flowmcp-schemas (https://github.com/FlowMCP/flowmcp-schemas) on GitHub. 2. **Create your schema** Write your schema following the Schema Creation Guide. Place it in the appropriate provider directory. 3. **Validate** Run validation against the FlowMCP specification: ```bash flowmcp validate ./your-schema.mjs ``` 4. **Test** Test with live API calls: ```bash flowmcp test single ./your-schema.mjs ``` 5. **Submit a pull request** Open a PR with your schema. Include the validation and test results in the PR description. ### Quality Standards All schemas in the library must meet these requirements: - **v2.0.0 format** with all required fields (`namespace`, `name`, `description`, `version`, `routes`) - **Output schemas** for all routes (`output.schema` with JSON Schema describing the response) - **Documentation links** in the `docs` field - **Tags** for discoverability - **Passing validation** via `flowmcp validate` - **Passing tests** via `flowmcp test single` (at least one route must return data) :::caution Schemas that fail validation or testing will not be merged. Run both `flowmcp validate` and `flowmcp test single` before submitting. ::: ## Links - **Schema Browser**: flowmcp.github.io/flowmcp-schemas (https://flowmcp.github.io/flowmcp-schemas/) - **GitHub**: FlowMCP/flowmcp-schemas (https://github.com/FlowMCP/flowmcp-schemas) - **FlowMCP Spec**: Specification Overview --- # x402 Payment Protocol /docs/ecosystem/x402 The x402 protocol (https://www.x402.org/) brings HTTP status code 402 (Payment Required) to life as a machine-readable payment protocol for APIs. The x402-mcp-validator (https://github.com/FlowMCP/x402-mcp-validator) validates MCP servers for x402 compliance -- connecting to servers, discovering tools, probing for payment requirements, and validating payment options against the spec. ## How x402 Works with MCP When an MCP tool requires payment, the flow is: 1. **Client calls a tool** on the MCP server 2. **Server responds with 402** including payment requirements (scheme, network, amount, asset, payTo) 3. **Client processes payment** through the specified payment network 4. **Client retries the tool call** with a payment receipt in the header 5. **Server validates payment** and executes the tool This enables API monetization at the tool level -- different tools can have different prices, and free tools can coexist with paid ones on the same server. ## x402 MCP Validator The validator connects to an MCP server end-to-end and returns a structured snapshot: ```javascript import { McpServerValidator } from 'x402-mcp-validator' const { status, messages, categories, entries } = await McpServerValidator.start( { endpoint: 'https://your-mcp-server.example.com/mcp', timeout: 15000 } ) console.log( `Status: ${status ? 'PASS' : 'FAIL'}` ) console.log( `Tools: ${entries['tools'].length}` ) console.log( `x402: ${categories['supportsX402']}` ) console.log( `Networks: ${JSON.stringify( entries['x402']['networks'] )}` ) ``` ### `.start()` Connects to an MCP server, discovers capabilities, probes for x402 payment support, validates payment requirements, measures latency, and returns a structured snapshot. | Key | Type | Description | Required | |-----|------|-------------|----------| | `endpoint` | string | MCP server URL | Yes | | `timeout` | number | Connection timeout in ms (default `10000`) | No | **Returns:** `{ status, messages, categories, entries }` ### `.compare()` Compares two snapshots from `.start()` and returns a structured diff with added, removed, and modified items per section. ```javascript const before = await McpServerValidator.start( { endpoint: 'https://server.example.com/mcp' } ) // ... time passes, server changes ... const after = await McpServerValidator.start( { endpoint: 'https://server.example.com/mcp' } ) const { status, messages, hasChanges, diff } = McpServerValidator.compare( { before, after } ) console.log( `Changes detected: ${hasChanges}` ) console.log( `Tools added: ${diff['tools']['added'].length}` ) console.log( `Tools removed: ${diff['tools']['removed'].length}` ) ``` | Key | Type | Description | Required | |-----|------|-------------|----------| | `before` | object | Snapshot from a previous `.start()` call | Yes | | `after` | object | Snapshot from a later `.start()` call | Yes | **Returns:** `{ status, messages, hasChanges, diff }` ## Categories The validator classifies each server with 12 boolean flags: | Flag | Description | |------|-------------| | `isReachable` | Server responded to HEAD request | | `supportsMcp` | MCP handshake completed | | `hasTools` | Server exposes at least one tool | | `hasResources` | Server exposes at least one resource | | `hasPrompts` | Server exposes at least one prompt | | `supportsX402` | At least one tool returned a 402 payment error | | `hasValidPaymentRequirements` | At least one payment option passed validation | | `supportsExactScheme` | Has payment options with `scheme: 'exact'` | | `supportsEvm` | Has payment options with `network: 'eip155:*'` | | `supportsSolana` | Has payment options with `network: 'solana:*'` | | `supportsTasks` | Server advertises tasks capability | | `supportsMcpApps` | Server advertises mcpApps capability | ## Validation Pipeline The validator processes an MCP server in six sequential steps: 1. **Connect** -- Connects to the MCP server via StreamableHTTP with SSE fallback. 2. **Discover** -- Discovers tools, resources, prompts, and capabilities through MCP protocol. 3. **Classify** -- Classifies server capabilities into the 12 boolean categories. 4. **Probe** -- Probes each tool for x402 payment requirements (HTTP 402 / JSON-RPC -32402). 5. **Validate** -- Validates payment options: scheme, network, amount, asset, payTo, checksums. 6. **Build Snapshot** -- Builds the structured snapshot with categories, entries, and validation messages. ## Validation Codes The validator uses structured error codes organized by category:
VAL -- Input Validation Codes `VAL-001` through `VAL-015` cover input parameter validation for both `.start()` and `.compare()` methods. Examples: missing endpoint, invalid URL format, missing snapshots for comparison.
CON -- MCP Connection Codes `CON-001` through `CON-011` cover server connectivity and MCP handshake issues. Examples: server not reachable, handshake failed, tools/list request failed.
PAY -- Payment Validation Codes `PAY-001` through `PAY-102` cover x402 payment requirement validation in detail. Checks include: x402 version, resource format, accepts array, scheme, network prefix, amount format, asset address, payTo address with checksum validation, and timeout values.
PRB -- Probe Codes `PRB-004` and `PRB-005` cover probe-level issues like unexpected exceptions and no tools available.
AUTH -- OAuth Codes `AUTH-002` through `AUTH-011` cover OAuth discovery including metadata, PKCE support, protected resource metadata, client registration, and scope detection.
CMP -- Comparison Codes `CMP-001` through `CMP-003` cover snapshot comparison integrity: different server endpoints, missing timestamps, and chronological ordering.
:::tip Each validation message includes an error code, severity (ERROR, WARNING, INFO), and a human-readable description. Use the codes to programmatically identify and handle specific issues. ::: ## Links - **GitHub**: FlowMCP/x402-mcp-validator (https://github.com/FlowMCP/x402-mcp-validator) - **x402 Spec**: x402.org (https://www.x402.org/) - **npm**: `npm install x402-mcp-validator` --- # How It Works /docs/getting-started/how-it-works ## Architecture Overview FlowMCP transforms declarative schema files into MCP tools that AI clients can call. Data flows through four layers: ``` Web Data Sources → Schemas → Core Runtime → MCP Server → AI Client (APIs) (.mjs) (FlowMCP) (stdio/HTTP) (Claude, etc.) ``` The schema layer is where you work. Everything else is handled by the runtime. ## The Four Steps 1. **Define** Write a schema as a `.mjs` file. Each schema declares one or more API tools with their endpoints, parameters, authentication, and expected responses. ```javascript // coingecko-ping.mjs export const main = { namespace: 'coingecko', name: 'Ping', description: 'Check CoinGecko API server status', version: '3.0.0', root: 'https://api.coingecko.com/api/v3', requiredServerParams: [], requiredLibraries: [], headers: {}, tools: { ping: { method: 'GET', path: '/ping', description: 'Check if CoinGecko API is online', parameters: [] } } } ``` 2. **Validate** FlowMCP Core validates your schema against validation rules covering structure, naming conventions, parameter formats, security constraints, and output declarations. ```javascript import { FlowMCP } from 'flowmcp-core' import { main } from './coingecko-ping.mjs' const { status, messages } = FlowMCP.validateSchema( { schema: main } ) // status: true — schema is valid // messages: [] — no validation errors ``` Validation catches issues at development time — before your schema reaches production. 3. **Activate** FlowMCP Core transforms your schema into MCP tools with auto-generated Zod validation for each parameter. One schema with 5 tools becomes 5 MCP tools. ```javascript import { Server } from '@modelcontextprotocol/sdk/server/index.js' const server = new Server( { name: 'my-server', version: '1.0.0' }, { capabilities: { tools: {} } } ) FlowMCP.activateServerTools( { server, schemas: [main] } ) // Registers: coingecko__ping as an MCP tool ``` 4. **Use** AI clients discover and call your tools through the MCP protocol. The client sees tool names, descriptions, and input schemas — everything needed to make informed tool calls. ``` AI Client: "What tools are available?" MCP Server: [coingecko__ping] — Check if CoinGecko API is online AI Client: calls coingecko__ping({}) MCP Server: { "gecko_says": "(V3) To the Moon!" } ``` ## Schema Anatomy Every FlowMCP v3.0.0 schema uses the **two-export pattern**: ### main (required) The `main` export is a plain JavaScript object that declares everything about your API endpoints. It is JSON-serializable and can be hashed for integrity verification. ```javascript export const main = { // Identity namespace: 'provider', // Provider name (lowercase) name: 'ToolName', // Human-readable name description: 'What it does', // Used by AI clients version: '3.0.0', // Schema format version // Connection root: 'https://api.example.com', // Base URL requiredServerParams: ['API_KEY'], // Server-side secrets requiredLibraries: [], // Allowed npm packages headers: { // Request headers 'Authorization': 'Bearer {{API_KEY}}' }, // Tools (API endpoints) tools: { toolName: { method: 'GET', path: '/endpoint/{{PARAM}}', description: 'What this tool does', parameters: [/* ... */], output: {/* ... */} } }, // Resources (optional, SQLite read-only data) resources: { resourceName: { description: 'Read-only data lookup', source: 'sqlite', database: 'data.db', queries: { /* ... */ } } }, // Skills (optional, AI agent instructions) skills: [ { name: 'skill-name', file: 'skill-name.mjs', description: 'What this skill does' } ] } ``` ### handlers (optional) The `handlers` export is a **factory function** that receives injected dependencies and returns handler functions keyed by tool name. Use it when API responses need transformation. ```javascript export const handlers = ( { sharedLists, libraries } ) => ({ toolName: { postProcess: ( { data } ) => { // Transform the raw API response const parsed = JSON.parse( data ) const summary = parsed.results .map( ( item ) => `${item.name}: ${item.value}` ) .join( '\n' ) return summary } } }) ``` The factory pattern ensures: - No free imports — dependencies are injected - Shared lists are available without file access - Libraries are pre-approved via `requiredLibraries` ## Parameter Flow When an AI client calls a FlowMCP tool, the request flows through several stages: ``` User Input → Zod Validation → URL Construction → API Call { "id": "bitcoin" } Validates types, Replaces {{ID}} in GET https://api. lengths, formats path and query coingecko.com/... ↓ MCP Response ← Handler (optional) ← Raw Response { content: [...] } postProcess() { "bitcoin": { ... } } transforms data ``` Each stage is deterministic: the same input always produces the same API call. Parameter validation uses Zod schemas auto-generated from the `parameters` array in your schema. ## Shared Lists Some parameter values are reusable across schemas — chain IDs, token symbols, protocol names. Instead of each schema defining these independently, FlowMCP injects **shared lists** at load-time. ```javascript // In the schema — reference a shared list parameters: [ { position: { key: 'chain', value: '{{CHAIN}}', location: 'insert' }, z: { primitive: 'enum()', options: ['$chainIds'] } // ^ injected at runtime } ] // In handlers — access shared lists export const handlers = ( { sharedLists } ) => ({ toolName: { postProcess: ( { data } ) => { const chainName = sharedLists.chainIds[data.chainId] return `Chain: ${chainName}` } } }) ``` This keeps schemas DRY and ensures consistency across providers. ## Security Model FlowMCP enforces security at the schema level: | Constraint | Purpose | |------------|---------| | **Zero imports** | Schemas cannot use `import` or `require` — all dependencies are injected | | **Library allowlist** | Only packages declared in `requiredLibraries` are available in handlers | | **Static scan** | Schemas are analyzed at load-time for forbidden patterns | | **Server params** | API keys stay server-side — never exposed to AI clients | | **Integrity hash** | The `main` export can be hashed to detect schema tampering | :::caution Schemas that attempt to import modules, access the filesystem, or use undeclared libraries are rejected at load-time. This is by design — it protects both the server operator and the AI client. ::: :::note For the full specification including all validation rules, parameter formats, and security details, see the Specification v3.0.0. ::: --- # Installation /docs/getting-started/installation ## Prerequisites - **Node.js 22+** — required for ES module support and modern JavaScript features - **npm** or **yarn** — for package management Check your Node.js version: ```bash node --version # Must be v22.x or higher ``` ## Core Library The core library provides schema validation, API execution, and MCP server activation. ```bash npm install github:FlowMCP/flowmcp-core ``` ```javascript import { FlowMCP } from 'flowmcp-core' ``` **Use FlowMCP Core when you want to:** - Validate schemas programmatically - Execute API calls via `FlowMCP.fetch()` - Build MCP servers with `FlowMCP.activateServerTools()` - Integrate FlowMCP into your own applications ## CLI Tool The CLI provides interactive access to the full schema catalog from the command line. ```bash npm install -g github:FlowMCP/flowmcp-cli ``` Verify the installation: ```bash flowmcp status ``` **Use the CLI when you want to:** - Search the schema catalog (`flowmcp search coingecko`) - Activate schemas for a project (`flowmcp add coingecko_ping`) - Call APIs directly (`flowmcp call coingecko_ping '{}'`) - Validate schemas during development :::note The CLI and Core library are independent packages. Install Core for programmatic use, CLI for interactive use, or both. ::: ## Schema Library 187+ production-ready schemas are available for immediate use. Browse the full catalog at: **flowmcp.github.io/flowmcp-schemas (https://flowmcp.github.io/flowmcp-schemas/)** Schemas cover providers including CoinGecko, Etherscan, Moralis, DeFi Llama, Dune Analytics, OpenWeather, GitHub, and many more. Each schema is validated, tested, and follows the v2.0.0 specification. With the CLI installed, you can search and activate schemas directly: ```bash flowmcp search ethereum # Shows matching schemas with descriptions flowmcp add get_contract_abi_etherscan # Activates the schema and shows its parameters ``` ## MCP SDK For building MCP servers, you also need the Model Context Protocol SDK: ```bash npm install @modelcontextprotocol/sdk ``` This provides the `Server`, `StdioServerTransport`, and `SSEServerTransport` classes needed to expose your schemas as MCP tools. ## Verify Installation ### Core Library Create a file called `verify.mjs`: ```javascript import { FlowMCP } from 'flowmcp-core' const schema = { namespace: 'test', name: 'Verify', description: 'Installation verification', version: '2.0.0', root: 'https://httpbin.org', requiredServerParams: [], requiredLibraries: [], headers: {}, routes: { check: { method: 'GET', path: '/get', description: 'Simple GET request', parameters: [] } } } const { status } = FlowMCP.validateSchema( { schema } ) console.log( status ? 'FlowMCP Core installed successfully!' : 'Validation failed' ) ``` ```bash node verify.mjs # → FlowMCP Core installed successfully! ``` ### CLI ```bash flowmcp status # Shows version, configuration, and active tools flowmcp search ping # Should return matching schemas from the catalog ``` :::caution FlowMCP requires Node.js 22 or higher. Earlier versions do not support the ES module features used by FlowMCP schemas. If `node --version` shows v20 or below, upgrade Node.js before proceeding. ::: ## Project Setup For a new project using FlowMCP, a minimal `package.json` looks like this: ```json { "name": "my-flowmcp-project", "version": "1.0.0", "type": "module", "dependencies": { "flowmcp-core": "github:FlowMCP/flowmcp-core", "@modelcontextprotocol/sdk": "latest" } } ``` :::tip The `"type": "module"` field is required for ES module support. FlowMCP schemas use `.mjs` files and `export` syntax. ::: ## Next Steps :::note[Quickstart] Create your first schema and call an API in 5 minutes. See Quickstart. ::: :::note[How It Works] Understand the architecture and data flow. See How It Works. ::: --- # Quickstart /docs/getting-started/quickstart ## Prerequisites - **Node.js 22+** — check with `node --version` - **npm** — comes with Node.js ## Create Your First Schema 1. **Install FlowMCP Core** Create a new project and install the core library: ```bash mkdir my-flowmcp-project cd my-flowmcp-project npm init -y npm install github:FlowMCP/flowmcp-core ``` Add `"type": "module"` to your `package.json` for ES module support. 2. **Write a Schema** Create a file called `coingecko-ping.mjs`: ```javascript export const main = { namespace: 'coingecko', name: 'Ping', description: 'Check CoinGecko API server status', version: '3.0.0', root: 'https://api.coingecko.com/api/v3', requiredServerParams: [], requiredLibraries: [], headers: {}, tools: { ping: { method: 'GET', path: '/ping', description: 'Check if CoinGecko API is online', parameters: [], output: { mimeType: 'application/json', schema: { type: 'object', properties: { gecko_says: { type: 'string', description: 'Response message' } } } } } } } ``` This schema declares a single tool that calls the CoinGecko ping endpoint. No API key required. 3. **Validate and Call** Create a file called `test.mjs`: ```javascript import { FlowMCP } from 'flowmcp-core' import { main } from './coingecko-ping.mjs' // Validate the schema const { status, messages } = FlowMCP.validateSchema( { schema: main } ) console.log( status ? 'Schema valid!' : messages ) // Call the API const result = await FlowMCP.fetch( { schema: main, routeName: 'ping', userParams: {}, serverParams: {} } ) console.log( result.dataAsString ) // → {"gecko_says":"(V3) To the Moon!"} ``` Run it: ```bash node test.mjs ``` You should see `Schema valid!` followed by the CoinGecko ping response. 4. **Run as MCP Server** Create a file called `server.mjs` to expose your schema as an MCP tool: ```javascript import { FlowMCP } from 'flowmcp-core' import { Server } from '@modelcontextprotocol/sdk/server/index.js' import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js' import { main } from './coingecko-ping.mjs' const server = new Server( { name: 'my-first-server', version: '1.0.0' }, { capabilities: { tools: {} } } ) FlowMCP.activateServerTools( { server, schemas: [main] } ) const transport = new StdioServerTransport() await server.connect( transport ) ``` Install the MCP SDK: ```bash npm install @modelcontextprotocol/sdk ``` Run the server: ```bash node server.mjs ``` Your MCP server is now running over stdio. AI clients like Claude Desktop can connect to it and call the `coingecko__ping` tool. :::tip The tool name is auto-generated from `namespace` + tool name: **coingecko__ping**. AI clients see this name along with the tool description to decide when to call it. ::: ## What Just Happened? 1. You declared an API endpoint as a schema (no server code needed) 2. FlowMCP validated the schema structure 3. FlowMCP called the API with correct URL construction and headers 4. FlowMCP exposed the schema as an MCP tool with auto-generated Zod validation The same pattern works for any REST API — add authentication via `requiredServerParams` and `headers`, add parameters via the `parameters` array, add response transformation via the `handlers` export. ## What's Next :::note[Schema Creation] Learn the full schema format with authentication, parameters, and handlers. See Schema Creation. ::: :::note[CLI Reference] Search, activate, and call 187+ pre-built schemas from the command line. See CLI Reference. ::: :::note[Examples] Real-world schema examples for common APIs. See Examples. ::: --- # What is FlowMCP? /docs/getting-started/what-is-flowmcp ## The Problem AI agents need tools — "get crypto prices", "check wallet balances", "query open data". But APIs are chaotic: different authentication methods, URL structures, response formats, and rate limits. Every integration requires custom server code, parameter validation, error handling, and response formatting. At 5 APIs this is tedious. At 50 it is unmaintainable. At 500 it is impossible without a systematic approach. ## The Solution FlowMCP is a **schema-driven normalization layer** that transforms any data source into MCP-compatible tools. You write a declarative `.mjs` schema. FlowMCP handles validation, URL construction, authentication, and response formatting. No custom server code. No boilerplate. One schema per provider. ## Four Primitives FlowMCP v3.0.0 supports four primitives in a single schema file: :::note[Tools] REST API endpoints (GET/POST/PUT/DELETE). Map parameters to URLs, inject authentication, validate inputs. The core primitive. ::: :::note[Resources] Local SQLite databases for bulk data and open data. Fast read-only queries via prepared statements — no network calls. ::: :::note[Prompts] Namespace descriptions that explain how to use tools effectively. Guide AI agents with domain context and usage patterns. ::: :::note[Skills] Multi-step workflow instructions. Reusable pipelines that compose tools and resources into higher-level operations. ::: ## Minimal Example A complete, runnable schema — everything an AI agent needs to call the CoinGecko price API: ```javascript export const main = { namespace: 'coingecko', name: 'CoinGecko Prices', description: 'Cryptocurrency price data from CoinGecko', version: '3.0.0', root: 'https://api.coingecko.com/api/v3', tools: { simplePrice: { method: 'GET', path: '/simple/price', description: 'Get current price of cryptocurrencies', parameters: { ids: { type: 'string', required: true, description: 'Coin IDs (comma-separated)' }, vs_currencies: { type: 'string', required: true, description: 'Target currencies' } } } } } ``` :::tip Most schemas only need the `main` export. An optional `handlers` export is available when API responses need transformation before reaching the AI agent. ::: ## Quickstart 1. **Install FlowMCP** ```bash npm install -g github:FlowMCP/flowmcp-cli ``` 2. **Search available schemas** FlowMCP ships with 450+ pre-built schemas across crypto, DeFi, open data, and more. ```bash flowmcp search coingecko ``` 3. **Add a tool to your project** Activates the tool and shows its expected input parameters. ```bash flowmcp add simple_price_coingecko ``` 4. **Call the tool** ```bash flowmcp call simple_price_coingecko '{"ids": "bitcoin", "vs_currencies": "usd"}' ``` :::note Some schemas require API keys configured in `~/.flowmcp/.env`. If a call fails due to missing keys, FlowMCP will tell you which variable to set. ::: ## How It Works FlowMCP separates each schema into two exports: | Export | Purpose | Description | |--------|---------|-------------| | `main` | Declarative config | JSON-serializable, hashable — describes tools, resources, prompts, and skills | | `handlers` | Executable logic | Optional factory function that transforms API responses | This separation enables integrity hashing (detect schema tampering), security scanning (analyze handlers before execution), and shared list injection (reusable value lists loaded at runtime). ## What's Next :::note[Installation] System requirements and setup instructions. See Installation. ::: :::note[CLI Reference] Complete command reference for search, add, call, validate, and test. See CLI Reference. ::: :::note[Schema Creation] Write your own schemas from scratch. See Schema Creation. ::: :::note[Specification] Full v3.0.0 specification with all primitives and validation rules. See Specification. ::: --- # Examples /docs/guides/examples These examples demonstrate the four most common FlowMCP schema patterns, from the simplest possible schema to multi-step async workflows. All examples use the v3.0.0 format. ## 1. Minimal Schema The simplest possible schema: a single tool with no parameters, no handlers, and no shared lists. This is the CoinGecko API ping endpoint. ```javascript // coingecko-ping.mjs export const main = { namespace: 'coingecko', name: 'Ping', description: 'Check CoinGecko API server status', version: '3.0.0', docs: [ 'https://docs.coingecko.com/reference/simple-ping' ], tags: [ 'utility', 'health' ], root: 'https://api.coingecko.com/api/v3', requiredServerParams: [], requiredLibraries: [], headers: {}, tools: { ping: { method: 'GET', path: '/ping', description: 'Check if CoinGecko API is online', parameters: [], output: { mimeType: 'application/json', schema: { type: 'object', properties: { gecko_says: { type: 'string', description: 'Response message from CoinGecko' } } } } } } } ``` **Key takeaways:** - No `requiredServerParams` -- this API needs no authentication - No `handlers` export -- the raw API response is returned as-is - The `output.schema` describes what the AI client receives - `parameters: []` means no user input is needed ## 2. Multi-Tool Schema with Handlers Multiple tools in one schema, with `postRequest` handlers that filter and reshape API responses. This wraps the DeFi Llama protocol analytics API. ### Main export ```javascript // defillama-protocols.mjs export const main = { namespace: 'defillama', name: 'ProtocolAnalytics', description: 'DeFi Llama protocol TVL and analytics data', version: '3.0.0', docs: [ 'https://defillama.com/docs/api' ], tags: [ 'defi', 'tvl', 'analytics' ], root: 'https://api.llama.fi', requiredServerParams: [], requiredLibraries: [], headers: {}, tools: { getProtocols: { method: 'GET', path: '/protocols', description: 'List all DeFi protocols with TVL data', parameters: [], output: { mimeType: 'application/json', schema: { type: 'array', items: { type: 'object', properties: { name: { type: 'string', description: 'Protocol name' }, slug: { type: 'string', description: 'URL-safe identifier' }, tvl: { type: 'number', description: 'Total value locked in USD' }, chain: { type: 'string', description: 'Primary chain' }, category: { type: 'string', description: 'Protocol category' } } } } } }, getTvl: { method: 'GET', path: '/tvl/{{protocolSlug}}', description: 'Get current TVL for a specific protocol', parameters: [ { position: { key: 'protocolSlug', value: '{{USER_PARAM}}', location: 'insert' }, z: { primitive: 'string()', options: [ 'min(1)' ] } } ], output: { mimeType: 'application/json', schema: { type: 'number' } } }, getProtocolTvl: { method: 'GET', path: '/protocol/{{protocolSlug}}', description: 'Get detailed TVL history for a protocol', parameters: [ { position: { key: 'protocolSlug', value: '{{USER_PARAM}}', location: 'insert' }, z: { primitive: 'string()', options: [ 'min(1)' ] } } ], output: { mimeType: 'application/json', schema: { type: 'object', properties: { name: { type: 'string', description: 'Protocol name' }, tvl: { type: 'array', items: { type: 'object' } }, currentChainTvls: { type: 'object', description: 'TVL per chain' } } } } }, getChainTvl: { method: 'GET', path: '/v2/historicalChainTvl/{{chainName}}', description: 'Get historical TVL for a specific chain', parameters: [ { position: { key: 'chainName', value: '{{USER_PARAM}}', location: 'insert' }, z: { primitive: 'string()', options: [ 'min(1)' ] } } ], output: { mimeType: 'application/json', schema: { type: 'array', items: { type: 'object', properties: { date: { type: 'number', description: 'Unix timestamp' }, tvl: { type: 'number', description: 'TVL in USD' } } } } } } } } ``` ### Handlers export ```javascript export const handlers = ( { sharedLists, libraries } ) => ( { getProtocols: { postRequest: async ( { response } ) => { const items = response .filter( ( item ) => item.tvl > 0 ) .map( ( item ) => { const { name, slug, tvl, chain, category } = item return { name, slug, tvl, chain, category } } ) return { response: items } } }, getProtocolTvl: { postRequest: async ( { response } ) => { const { name, tvl, currentChainTvls } = response const simplified = { name, tvl: tvl || [], currentChainTvls: currentChainTvls || {} } return { response: simplified } } } } ) ``` **Key takeaways:** - Four tools in one schema covering related endpoints - `location: 'insert'` substitutes parameters into the URL path - The `handlers` export transforms responses for two of the four tools - Tools without handlers return the raw API response - The handler factory receives `{ sharedLists, libraries }` even when unused ## 3. Shared List Schema Demonstrates shared list references and `{{listName:fieldName}}` interpolation. This Etherscan gas tracker schema uses the `evmChains` shared list to generate a chain selector enum. ### Schema ```javascript // etherscan-gas.mjs export const main = { namespace: 'etherscan', name: 'GasTracker', description: 'EVM gas price tracking via Etherscan API', version: '3.0.0', docs: [ 'https://docs.etherscan.io/api-endpoints/gas-tracker' ], tags: [ 'evm', 'gas', 'transactions' ], root: 'https://api.etherscan.io/v2/api', requiredServerParams: [ 'ETHERSCAN_API_KEY' ], requiredLibraries: [], headers: {}, sharedLists: [ { ref: 'evmChains', version: '1.0.0', filter: { key: 'etherscanAlias', exists: true } } ], tools: { getGasOracle: { method: 'GET', path: '/api', description: 'Get current gas prices for an EVM chain', parameters: [ { position: { key: 'chainName', value: '{{USER_PARAM}}', location: 'query' }, z: { primitive: 'enum({{evmChains:etherscanAlias}})', options: [] } }, { position: { key: 'module', value: 'gastracker', location: 'query' }, z: { primitive: 'string()', options: [] } }, { position: { key: 'action', value: 'gasoracle', location: 'query' }, z: { primitive: 'string()', options: [] } }, { position: { key: 'apikey', value: '{{SERVER_PARAM:ETHERSCAN_API_KEY}}', location: 'query' }, z: { primitive: 'string()', options: [] } } ], output: { mimeType: 'application/json', schema: { type: 'object', properties: { LastBlock: { type: 'string', description: 'Latest block number' }, SafeGasPrice: { type: 'string', description: 'Safe gas price in Gwei' }, ProposeGasPrice: { type: 'string', description: 'Proposed gas price in Gwei' }, FastGasPrice: { type: 'string', description: 'Fast gas price in Gwei' } } } } } } } ``` ### Shared list definition (evmChains) ```javascript // Shared list: evmChains v1.0.0 export const list = { meta: { name: 'evmChains', version: '1.0.0', description: 'Unified EVM chain registry with provider-specific aliases', fields: [ { key: 'alias', type: 'string', description: 'Canonical chain alias' }, { key: 'chainId', type: 'number', description: 'EVM chain ID' }, { key: 'name', type: 'string', description: 'Human-readable chain name' }, { key: 'etherscanAlias', type: 'string', optional: true, description: 'Etherscan API chain parameter' }, { key: 'moralisChainSlug', type: 'string', optional: true, description: 'Moralis chain slug' } ], dependsOn: [] }, entries: [ { alias: 'ETHEREUM_MAINNET', chainId: 1, name: 'Ethereum Mainnet', etherscanAlias: 'ETH', moralisChainSlug: 'eth' }, { alias: 'POLYGON_MAINNET', chainId: 137, name: 'Polygon Mainnet', etherscanAlias: 'POLYGON', moralisChainSlug: 'polygon' }, { alias: 'ARBITRUM_ONE', chainId: 42161, name: 'Arbitrum One', etherscanAlias: 'ARBITRUM', moralisChainSlug: 'arbitrum' }, { alias: 'BASE_MAINNET', chainId: 8453, name: 'Base Mainnet', etherscanAlias: 'BASE', moralisChainSlug: 'base' }, { alias: 'AVALANCHE_C_CHAIN', chainId: 43114, name: 'Avalanche C-Chain', etherscanAlias: null, moralisChainSlug: 'avalanche' } ] } ``` **Key takeaways:** - `sharedLists` declares which shared lists this schema needs - `filter: { key: 'etherscanAlias', exists: true }` excludes chains without an Etherscan alias (Avalanche is filtered out) - `enum({{evmChains:etherscanAlias}})` generates `enum(["ETH","POLYGON","ARBITRUM","BASE","BSC"])` at load time - `{{SERVER_PARAM:ETHERSCAN_API_KEY}}` injects the API key from the environment - Fixed parameters like `module` and `action` have hardcoded values (not `{{USER_PARAM}}`) ## 4. Async Workflow Schema A multi-step API workflow with execute, poll status, and retrieve results. This wraps the Dune Analytics query execution pipeline. ```javascript // dune-query-engine.mjs export const main = { namespace: 'dune', name: 'QueryEngine', description: 'Execute and retrieve Dune Analytics SQL queries', version: '3.0.0', docs: [ 'https://docs.dune.com/api-reference/executions' ], tags: [ 'analytics', 'sql', 'blockchain' ], root: 'https://api.dune.com', requiredServerParams: [ 'DUNE_API_KEY' ], requiredLibraries: [], headers: {}, tools: { executeQuery: { method: 'POST', path: '/api/v1/query/{{queryId}}/execute', description: 'Execute a saved Dune query', parameters: [ { position: { key: 'queryId', value: '{{USER_PARAM}}', location: 'insert' }, z: { primitive: 'number()', options: [ 'min(1)' ] } }, { position: { key: 'x-dune-api-key', value: '{{SERVER_PARAM:DUNE_API_KEY}}', location: 'body' }, z: { primitive: 'string()', options: [] } } ], output: { mimeType: 'application/json', schema: { type: 'object', properties: { execution_id: { type: 'string', description: 'Unique execution identifier' }, state: { type: 'string', description: 'Current execution state' } } } } }, getExecutionStatus: { method: 'GET', path: '/api/v1/execution/{{executionId}}/status', description: 'Check the status of a query execution', parameters: [ { position: { key: 'executionId', value: '{{USER_PARAM}}', location: 'insert' }, z: { primitive: 'string()', options: [] } }, { position: { key: 'x-dune-api-key', value: '{{SERVER_PARAM:DUNE_API_KEY}}', location: 'body' }, z: { primitive: 'string()', options: [] } } ], output: { mimeType: 'application/json', schema: { type: 'object', properties: { execution_id: { type: 'string' }, state: { type: 'string', description: 'Current state' }, queue_position: { type: 'number', description: 'Position in execution queue', nullable: true } } } } }, getExecutionResults: { method: 'GET', path: '/api/v1/execution/{{executionId}}/results', description: 'Get the results of a completed query execution', parameters: [ { position: { key: 'executionId', value: '{{USER_PARAM}}', location: 'insert' }, z: { primitive: 'string()', options: [] } }, { position: { key: 'x-dune-api-key', value: '{{SERVER_PARAM:DUNE_API_KEY}}', location: 'body' }, z: { primitive: 'string()', options: [] } } ], output: { mimeType: 'application/json', schema: { type: 'object', properties: { execution_id: { type: 'string' }, state: { type: 'string' }, result: { type: 'object', properties: { rows: { type: 'array', items: { type: 'object' } }, metadata: { type: 'object', properties: { column_names: { type: 'array', items: { type: 'string' } }, total_row_count: { type: 'number' } } } } } } } } } } } ``` **Key takeaways:** - Three tools form an async workflow: execute, poll, retrieve - The AI agent calls `executeQuery` first to get an `execution_id` - Then polls `getExecutionStatus` until `state` is `"QUERY_STATE_COMPLETED"` - Finally retrieves results with `getExecutionResults` - Each tool uses `{{SERVER_PARAM:DUNE_API_KEY}}` for authentication - The AI client orchestrates the multi-step flow using the tool descriptions :::note Async workflow schemas work naturally with AI agents. The agent reads the tool descriptions, understands the execute-poll-retrieve pattern, and orchestrates the calls in sequence. ::: ## Pattern Summary | Pattern | When to use | Example | |---------|-------------|---------| | **Minimal** | Simple endpoints with no auth | Health checks, public APIs | | **Multi-Tool** | Related endpoints from one API | Protocol analytics, user management | | **Shared List** | Multi-chain or multi-provider schemas | EVM chain selection, exchange lists | | **Async Workflow** | APIs with execute/poll/retrieve patterns | Query engines, batch processing | --- # Schema Creation /docs/guides/schema-creation This guide walks you through creating FlowMCP v3.0.0 schemas. A schema is a `.mjs` file that describes how to interact with a REST API -- what endpoints exist, what parameters they accept, and how responses should be transformed. ## Prerequisites Before creating a schema, you need: - The API documentation for the service you want to wrap - An API key if the service requires authentication - Node.js 18+ installed - FlowMCP CLI installed (`npm install -g github:FlowMCP/flowmcp-cli`) ## Creation Process 1. **Choose namespace and identify endpoints** Pick a unique namespace for your schema and list the API endpoints you want to expose. The namespace becomes part of the tool name: `namespace_toolName`. Keep it short and recognizable (e.g., `coingecko`, `etherscan`, `defillama`). ```javascript // Namespace: "myapi" // Endpoints to wrap: // GET /api/v1/status -> ping // GET /api/v1/data/:id -> getData ``` 2. **Create the main export** Every schema exports a `main` object with the API definition: ```javascript export const main = { namespace: 'myapi', name: 'MyAPI', description: 'Access data from MyAPI service', version: '3.0.0', docs: [ 'https://docs.myapi.com' ], tags: [ 'data', 'utility' ], root: 'https://api.myapi.com/v1', requiredServerParams: [ 'MYAPI_KEY' ], requiredLibraries: [], headers: {}, tools: { ping: { method: 'GET', path: '/status', description: 'Check if MyAPI is online', parameters: [], output: { mimeType: 'application/json', schema: { type: 'object', properties: { status: { type: 'string', description: 'Server status' } } } } }, getData: { method: 'GET', path: '/data/{{id}}', description: 'Get data record by ID', parameters: [ { position: { key: 'id', value: '{{USER_PARAM}}', location: 'insert' }, z: { primitive: 'string()', options: [ 'min(1)' ] } }, { position: { key: 'apikey', value: '{{SERVER_PARAM:MYAPI_KEY}}', location: 'query' }, z: { primitive: 'string()', options: [] } } ], output: { mimeType: 'application/json', schema: { type: 'object', properties: { id: { type: 'string' }, value: { type: 'number' } } } } } } } ``` 3. **Add output schemas** Each tool can declare its response structure in the `output` field. This tells AI clients what to expect: ```javascript output: { mimeType: 'application/json', schema: { type: 'object', properties: { name: { type: 'string', description: 'Protocol name' }, tvl: { type: 'number', description: 'Total value locked in USD' } } } } ``` :::tip Output schemas are optional but strongly recommended. They help AI clients understand what data the tool returns, leading to better tool selection and response handling. ::: 4. **Add handlers (optional)** If the raw API response needs transformation, add a `handlers` export. This is a factory function that receives shared lists and libraries: ```javascript export const handlers = ( { sharedLists, libraries } ) => ( { getData: { postRequest: async ( { response } ) => { const { id, rawValue, metadata } = response const simplified = { id, value: rawValue / 100, source: metadata.provider } return { response: simplified } } } } ) ``` Handlers support two hooks per tool: - `preRequest` -- modify the request before it is sent - `postRequest` -- transform the response before it reaches the AI client 5. **Validate with CLI** Run the schema through the validation pipeline: ```bash flowmcp validate ./my-schema.mjs ``` The validator checks rules covering structure, security, and correctness. 6. **Test with CLI** Execute live API calls to verify the schema works: ```bash flowmcp test single ./my-schema.mjs flowmcp test single ./my-schema.mjs --route getData ``` ## Parameter Patterns Parameters define how user input and server credentials map to API requests. Each parameter has a `position` that controls where it goes: ### Query Parameters Appended to the URL as `?key=value`: ```javascript { position: { key: 'symbol', value: '{{USER_PARAM}}', location: 'query' }, z: { primitive: 'string()', options: [ 'min(1)' ] } } // GET /api/data?symbol=BTC ``` ### Path Parameters (insert) Substituted into the URL path: ```javascript { position: { key: 'userId', value: '{{USER_PARAM}}', location: 'insert' }, z: { primitive: 'string()', options: [ 'min(1)' ] } } // path: '/users/{{userId}}' -> /users/abc123 ``` ### Body Parameters Sent in the request body for POST/PUT requests: ```javascript { position: { key: 'query', value: '{{USER_PARAM}}', location: 'body' }, z: { primitive: 'string()', options: [] } } ``` ### Server Parameters Injected from environment variables. Never exposed to the AI client: ```javascript { position: { key: 'apikey', value: '{{SERVER_PARAM:ETHERSCAN_API_KEY}}', location: 'query' }, z: { primitive: 'string()', options: [] } } ``` :::note The `{{SERVER_PARAM:KEY_NAME}}` syntax references a key declared in `requiredServerParams`. The runtime injects the value from the environment at execution time. ::: ## Zod Validation Each parameter includes a `z` field that defines validation rules: ```javascript // String with minimum length z: { primitive: 'string()', options: [ 'min(1)' ] } // Number with minimum value z: { primitive: 'number()', options: [ 'min(1)' ] } // Enum from a fixed list z: { primitive: 'enum(["bitcoin","ethereum","solana"])', options: [] } // Enum from a shared list field z: { primitive: 'enum({{evmChains:etherscanAlias}})', options: [] } // Optional string z: { primitive: 'string()', options: [ 'optional()' ] } ``` ## Shared List References Schemas can reference shared lists for reusable value enumerations like chain IDs or token symbols: ```javascript // In main: sharedLists: [ { ref: 'evmChains', version: '1.0.0', filter: { key: 'etherscanAlias', exists: true } } ], // In a parameter: z: { primitive: 'enum({{evmChains:etherscanAlias}})', options: [] } ``` The `{{evmChains:etherscanAlias}}` syntax interpolates the `etherscanAlias` field from all entries in the `evmChains` shared list that pass the filter. This generates an enum like `enum(["ETH","POLYGON","ARBITRUM","OPTIMISM","BASE","BSC"])`. ## Handler Patterns ### Response Filtering Reduce large API responses to the fields the AI client needs: ```javascript export const handlers = ( { sharedLists, libraries } ) => ( { getProtocols: { postRequest: async ( { response } ) => { const items = response .filter( ( item ) => item.tvl > 0 ) .map( ( item ) => { const { name, slug, tvl, chain, category } = item return { name, slug, tvl, chain, category } } ) return { response: items } } } } ) ``` ### Pre-Request Modification Modify request parameters before the API call: ```javascript export const handlers = ( { sharedLists, libraries } ) => ( { getData: { preRequest: async ( { params } ) => { const { symbol } = params const normalized = symbol.toUpperCase() return { params: { ...params, symbol: normalized } } } } } ) ``` :::tip Keep handlers simple. Their purpose is data transformation, not business logic. If your handler is growing complex, consider splitting the schema into multiple tools. ::: ## Best Practices :::note[One concern per schema] Group related endpoints into a single schema. A schema for "Etherscan Gas Tracker" should contain gas-related tools, not all Etherscan endpoints. ::: :::note[Descriptive tool names] Use verb-noun format: `getBalance`, `listProtocols`, `executeQuery`. The tool name becomes part of the MCP tool name. ::: :::note[Include output schemas] Always define `output.schema` for each tool. This helps AI clients understand what data they will receive and select the right tool. ::: :::note[Test with real data] Use `flowmcp test single` to verify against the real API. Schema validation alone cannot catch API-side issues. ::: :::caution[Common Mistakes] - Forgetting `requiredServerParams` when using `{{SERVER_PARAM:...}}` in parameters - Using `location: 'insert'` without a matching `{{key}}` placeholder in the path - Declaring `requiredLibraries` without a corresponding `handlers` export that uses them - Omitting the `version: '3.0.0'` field (required for v3 schemas) ::: --- # MCP Server Integration /docs/guides/server-integration FlowMCP schemas can be served as MCP tools through two transport modes: **stdio** for local AI applications like Claude Desktop, and **HTTP/SSE** for remote web applications. This guide covers both approaches. ## Overview The integration path depends on your use case: | Transport | Use Case | Protocol | |-----------|----------|----------| | **stdio** | Claude Desktop, Claude Code, local AI apps | Standard input/output | | **HTTP/SSE** | Web services, remote clients, multi-tenant | Server-Sent Events over HTTP | | **CLI** | Quick testing, agent mode | `flowmcp run` | ### Local Server (stdio) The stdio transport is used for local AI applications that launch the MCP server as a subprocess. This is the standard approach for Claude Desktop integration. ```javascript import { FlowMCP } from 'flowmcp-core' import { Server } from '@modelcontextprotocol/sdk/server/index.js' import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js' // Import your schemas import { main as coingeckoMain } from './schemas/coingecko-ping.mjs' import { main as etherscanMain, handlers as etherscanHandlers } from './schemas/etherscan-gas.mjs' // Create MCP server const server = new Server( { name: 'my-flowmcp-server', version: '1.0.0' }, { capabilities: { tools: {} } } ) // Server params (API keys from environment) const serverParams = { ETHERSCAN_API_KEY: process.env.ETHERSCAN_API_KEY } // Load and activate schemas const { status: s1, main: m1, handlerMap: h1 } = await FlowMCP.loadSchema( { filePath: './schemas/coingecko-ping.mjs' } ) const { status: s2, main: m2, handlerMap: h2 } = await FlowMCP.loadSchema( { filePath: './schemas/etherscan-gas.mjs' } ) // Activate all schema routes as MCP tools FlowMCP.activateServerTools( { server, schema: m1, serverParams, validate: true } ) FlowMCP.activateServerTools( { server, schema: m2, serverParams, validate: true } ) // Connect via stdio const transport = new StdioServerTransport() await server.connect( transport ) ``` :::note The stdio transport communicates over standard input/output. The AI application launches your server as a child process and sends MCP protocol messages through the pipe. ::: ### Remote Server (HTTP/SSE) For web applications and remote access, use the SSE transport with an HTTP server: ```javascript import express from 'express' import { FlowMCP } from 'flowmcp-core' import { Server } from '@modelcontextprotocol/sdk/server/index.js' import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js' const app = express() // Create MCP server const server = new Server( { name: 'my-remote-server', version: '1.0.0' }, { capabilities: { tools: {} } } ) const serverParams = { ETHERSCAN_API_KEY: process.env.ETHERSCAN_API_KEY } // Load and activate schemas const { main } = await FlowMCP.loadSchema( { filePath: './schemas/etherscan-gas.mjs' } ) FlowMCP.activateServerTools( { server, schema: main, serverParams } ) // SSE endpoint app.get( '/sse', async ( req, res ) => { const transport = new SSEServerTransport( '/messages', res ) await server.connect( transport ) } ) // Message endpoint app.post( '/messages', async ( req, res ) => { await transport.handlePostMessage( req, res ) } ) app.listen( 3000, () => { console.log( 'MCP server running on http://localhost:3000' ) } ) ``` ### CLI (flowmcp run) The fastest way to serve schemas is through the CLI: ```bash # Serve default group as MCP server (stdio) flowmcp run # Serve specific group flowmcp run --group crypto ``` This starts the CLI in MCP server mode using stdio transport. Configure it in your AI application's MCP settings. ## Claude Desktop Configuration To use FlowMCP schemas in Claude Desktop, add your server to `claude_desktop_config.json`: ### Custom Server (stdio) ```json { "mcpServers": { "flowmcp-crypto": { "command": "node", "args": [ "/path/to/your/server.mjs" ], "env": { "ETHERSCAN_API_KEY": "your-key-here", "COINGECKO_API_KEY": "your-key-here" } } } } ``` ### FlowMCP CLI ```json { "mcpServers": { "flowmcp": { "command": "flowmcp", "args": [ "run", "--group", "crypto" ] } } } ``` The config file is located at: - **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json` - **Windows**: `%APPDATA%\Claude\claude_desktop_config.json` :::tip After editing the config, restart Claude Desktop to pick up the new MCP server configuration. ::: ## Schema Filtering When serving many schemas, use filtering to expose only the tools you need: ```javascript import { FlowMCP } from 'flowmcp-core' const allSchemas = [ schema1, schema2, schema3, schema4 ] // Filter by namespace const { filteredArrayOfSchemas } = FlowMCP.filterArrayOfSchemas( { arrayOfSchemas: allSchemas, includeNamespaces: [ 'coingecko', 'etherscan' ], excludeNamespaces: [], activateTags: [] } ) // Filter by tags const { filteredArrayOfSchemas: defiSchemas } = FlowMCP.filterArrayOfSchemas( { arrayOfSchemas: allSchemas, includeNamespaces: [], excludeNamespaces: [], activateTags: [ 'defi' ] } ) // Filter specific routes const { filteredArrayOfSchemas: specific } = FlowMCP.filterArrayOfSchemas( { arrayOfSchemas: allSchemas, includeNamespaces: [], excludeNamespaces: [], activateTags: [ 'coingecko.getPrice', // Include only getPrice from coingecko 'etherscan.!getBalance' // Exclude getBalance from etherscan ] } ) // Activate filtered schemas specific.forEach( ( schema ) => { FlowMCP.activateServerTools( { server, schema, serverParams } ) } ) ``` ## Server Parameters Server parameters (API keys, tokens) are injected at runtime and never exposed to the AI client. Declare them in the schema's `requiredServerParams` and pass them when activating tools: ```javascript // Schema declares what it needs export const main = { // ... requiredServerParams: [ 'ETHERSCAN_API_KEY', 'MORALIS_API_KEY' ], // ... } // Server provides the values const serverParams = { ETHERSCAN_API_KEY: process.env.ETHERSCAN_API_KEY, MORALIS_API_KEY: process.env.MORALIS_API_KEY } FlowMCP.activateServerTools( { server, schema: main, serverParams } ) ``` :::caution If a required server parameter is missing, the tool will fail at execution time with a clear error message. Always verify your environment variables are set before starting the server. ::: ## Activating Individual Routes For fine-grained control, activate individual routes instead of entire schemas: ```javascript // Activate a single route as an MCP tool const { toolName, mcpTool } = FlowMCP.activateServerTool( { server, schema: main, routeName: 'getGasOracle', serverParams, validate: true } ) console.log( `Activated: ${toolName}` ) // Output: "Activated: etherscan_getGasOracle" ``` ## Inspecting Tools Before Activation Use `prepareServerTool` to inspect a tool configuration without registering it: ```javascript const toolConfig = FlowMCP.prepareServerTool( { schema: main, serverParams, routeName: 'getGasOracle', validate: true } ) console.log( 'Tool name:', toolConfig.toolName ) console.log( 'Description:', toolConfig.description ) console.log( 'Zod schema:', toolConfig.zod ) // Execute manually if needed const result = await toolConfig.func( { chainName: 'ETH' } ) ``` --- # CLI Reference /docs/reference/cli-reference The FlowMCP CLI is a command-line tool for developing, validating, and managing FlowMCP schemas. It handles schema imports, group management, live API testing, migration, and can run as an MCP server for AI agent integration. ## Installation ```bash npm install -g github:FlowMCP/flowmcp-cli ``` Or clone and link locally: ```bash git clone https://github.com/FlowMCP/flowmcp-cli.git cd flowmcp-cli npm install npm link ``` ## Architecture The CLI operates on two configuration levels: | Level | Path | Content | |-------|------|---------| | **Global** | `~/.flowmcp/` | Config, `.env` with API keys, all imported schemas | | **Local** | `{project}/.flowmcp/` | Project config, groups with selected tools | ``` ~/.flowmcp/ ├── config.json # Global configuration ├── .env # API keys for schema testing └── schemas/ # All imported schema files └── flowmcp-schemas/ # Imported from GitHub {project}/.flowmcp/ ├── config.json # Project-level configuration └── groups/ # Tool groups for this project ``` ## Two Modes The CLI has two operating modes that control which commands are available: | Mode | Default | Description | |------|---------|-------------| | **Agent** | Yes | Subset of commands for AI agent use (search, add, remove, list, call, run) | | **Dev** | No | All commands including validation, testing, schema browsing, migration, and imports | Switch modes with `flowmcp mode agent` or `flowmcp mode dev`. :::note Agent mode is the default. It exposes only the commands an AI agent needs to discover, activate, and call tools. Switch to Dev mode for schema development and validation workflows. ::: ## Commands ### Setup #### `flowmcp init` Interactive setup that creates global and local configuration. Run this once in each project. ```bash flowmcp init ``` This will: - Create `~/.flowmcp/` if it does not exist - Optionally import the default schema repository - Create `.flowmcp/` in the current project - Set up a default group #### `flowmcp status` Show config, sources, groups, and health info. ```bash flowmcp status ``` #### `flowmcp mode [agent|dev]` Show or switch the current mode. ```bash flowmcp mode # Show current mode flowmcp mode dev # Switch to dev mode flowmcp mode agent # Switch to agent mode ``` ### Discovery #### `flowmcp search ` Find available tools by keyword. Returns matching tool names with descriptions. ```bash flowmcp search etherscan flowmcp search "gas price" flowmcp search defi ``` #### `flowmcp schemas` List all available schemas and their tools. **Dev mode only.** ```bash flowmcp schemas ``` :::caution The `schemas` command can produce long output with large schema collections. Use `search` first to narrow down results. ::: ### Schema Management #### `flowmcp add ` Activate a tool for this project. Adds it to the default group. ```bash flowmcp add coingecko_simplePrice ``` #### `flowmcp remove ` Deactivate a tool from the project. ```bash flowmcp remove coingecko_simplePrice ``` #### `flowmcp list` Show all active tools in the current project. ```bash flowmcp list ``` #### `flowmcp import [--branch name]` Import schemas from a GitHub repository. **Dev mode only.** ```bash flowmcp import https://github.com/FlowMCP/flowmcp-schemas flowmcp import https://github.com/FlowMCP/flowmcp-schemas --branch develop ``` #### `flowmcp import-registry ` Import schemas from a registry URL. **Dev mode only.** ```bash flowmcp import-registry https://registry.example.com/schemas ``` #### `flowmcp update [source-name]` Update schemas from remote registries using hash-based delta sync. ```bash flowmcp update # Update all sources flowmcp update flowmcp-schemas # Update specific source ``` ### Migration #### `flowmcp migrate [flags]` Migrate schemas from v2 to v3 format. Renames `routes` to `tools` and updates the version field. **Dev mode only.** ```bash # Migrate a single schema file flowmcp migrate ./schemas/coingecko/Ping.mjs # Migrate all schemas in a directory flowmcp migrate --all ./schemas/ # Preview changes without writing files flowmcp migrate --dry-run ./schemas/coingecko/Ping.mjs ``` **Flags:** | Flag | Description | |------|-------------| | `--all` | Migrate all `.mjs` schema files in the directory recursively | | `--dry-run` | Preview changes without modifying any files | **What it does:** 1. Reads the schema file 2. Renames `routes` key to `tools` 3. Updates `version` from `2.x.x` to `3.0.0` 4. Writes the updated file in-place 5. Runs validation on the result :::tip Always use `--dry-run` first to preview changes before modifying files. The migrate command modifies files in-place. ::: ### Group Management Groups organize tools into named collections. Each project can have multiple groups with one set as default. #### `flowmcp group list` List all groups and their tool counts. ```bash flowmcp group list ``` #### `flowmcp group append --tools "refs"` Add tools to a group. Creates the group if it does not exist. ```bash flowmcp group append crypto --tools "flowmcp-schemas/coingecko/simplePrice.mjs,flowmcp-schemas/etherscan/getBalance.mjs" ``` #### `flowmcp group remove --tools "refs"` Remove tools from a group. ```bash flowmcp group remove crypto --tools "flowmcp-schemas/coingecko/simplePrice.mjs" ``` #### `flowmcp group set-default ` Set the default group used by `call`, `test`, and `run` commands. ```bash flowmcp group set-default crypto ``` ### Validation & Testing (Dev Mode) #### `flowmcp validate [path] [--group name]` Validate schema structure against the FlowMCP specification. ```bash flowmcp validate # Validate default group flowmcp validate ./my-schema.mjs # Validate single file flowmcp validate --group crypto # Validate specific group ``` #### `flowmcp test project [--route name] [--group name]` Test default group schemas with live API calls. ```bash flowmcp test project # Test all tools flowmcp test project --route getBalance # Test specific tool flowmcp test project --group crypto # Test specific group ``` #### `flowmcp test user [--route name]` Test all user-created schemas with live API calls. ```bash flowmcp test user ``` #### `flowmcp test single [--route name]` Test a single schema file. ```bash flowmcp test single ./my-schema.mjs flowmcp test single ./my-schema.mjs --route getBalance ``` ### Execution #### `flowmcp call list-tools [--group name]` List available tools in the default or specified group. ```bash flowmcp call list-tools flowmcp call list-tools --group crypto ``` #### `flowmcp call [json] [--group name]` Call a tool with optional JSON input. ```bash flowmcp call coingecko_simplePrice '{"ids":"bitcoin","vs_currencies":"usd"}' flowmcp call etherscan_getBalance '{"address":"0x..."}' --group crypto ``` #### `flowmcp run [--group name]` Start as an MCP server using stdio transport. This is used for integration with AI agent frameworks like Claude Code. ```bash flowmcp run flowmcp run --group crypto ``` :::tip Use `flowmcp run` to connect the CLI directly to Claude Desktop or other MCP-compatible AI clients via stdio transport. ::: ## Tool Reference Format Tools are referenced using the `source/file.mjs` format with optional type discriminators: ``` source/file.mjs # All tools from a schema source/file.mjs::tool::toolName # Single tool (v3 format) source/file.mjs::resource::resName # Single resource (v3 format) source/file.mjs::skill::skillName # Single skill (v3 format) source/file.mjs::toolName # Single tool (v2 compat format) ``` For example: ``` flowmcp-schemas/coingecko/simplePrice.mjs # All tools flowmcp-schemas/coingecko/simplePrice.mjs::tool::getPrice # Single tool ``` ## Workflow Example 1. **Initialize** Run the interactive setup. This imports schemas and creates a default group. ```bash flowmcp init ``` 2. **Import schemas (optional)** If you skipped the import during init, add schemas manually: ```bash flowmcp import https://github.com/FlowMCP/flowmcp-schemas ``` 3. **Create a group** Organize tools into a named group: ```bash flowmcp group append crypto --tools "flowmcp-schemas/coingecko/simplePrice.mjs,flowmcp-schemas/etherscan/getBalance.mjs" flowmcp group set-default crypto ``` 4. **Validate and test** Ensure schemas are correct and APIs respond: ```bash flowmcp validate flowmcp test project ``` 5. **Use tools** Call tools directly or start the MCP server: ```bash flowmcp call list-tools flowmcp call coingecko_simplePrice '{"ids":"bitcoin","vs_currencies":"usd"}' # Or start as MCP server flowmcp run ``` ## Environment Variables API keys for schema testing go in `~/.flowmcp/.env`: ```bash ETHERSCAN_API_KEY=your_key_here COINGECKO_API_KEY=your_key_here DUNE_API_KEY=your_key_here ``` :::caution Never commit API keys to version control. The `.env` file in `~/.flowmcp/` is your global key store and should stay on your machine only. ::: --- # Core API Methods /docs/reference/core-methods Complete reference for all public methods in `flowmcp-core`. Methods are organized by usage category. All methods are static. ```javascript import { FlowMCP } from 'flowmcp-core' ``` :::note FlowMCP Core exports both v3 (default) and legacy APIs. This reference covers the current v3 API. For v1 methods, import from `flowmcp-core/v1`. ::: ## Method Overview | Method | Purpose | Returns | |--------|---------|---------| | `.loadSchema()` | Load and validate a schema file | `{ status, main, handlerMap }` | | `.validateMain()` | Validate a main export against validation rules | `{ status, messages }` | | `.scanSecurity()` | Run security scan on a schema file | `{ status, messages }` | | `.fetch()` | Execute an API request for a tool | `{ status, dataAsString, messages }` | | `.resolveSharedLists()` | Resolve shared list references | `{ sharedLists }` | | `.interpolateEnum()` | Interpolate shared list values into enum templates | `{ result }` | | `.loadLibraries()` | Load declared libraries from allowlist | `{ libraries }` | | `.createHandlers()` | Create handler map from factory function | `{ handlerMap }` | | `.detectLegacy()` | Detect if a module uses v1 format | `{ isLegacy, format }` | | `.adaptLegacy()` | Convert a v1 schema to v2 format | `{ main, handlersFn, hasHandlers, warnings }` | | `.getDefaultAllowlist()` | Get the default library allowlist | `{ allowlist }` | | `.generateOutputSchema()` | Generate output schema from API response | `{ output }` | --- ## Schema Loading & Validation ### .loadSchema() Loads a `.mjs` schema file, runs security scanning, validates the `main` export, resolves shared lists, loads declared libraries, creates the handler map, and processes any resources and skills. This is the primary entry point for working with schemas. **Method** ```javascript const result = await FlowMCP.loadSchema( { filePath, listsDir, allowlist } ) ``` **Parameters** | Key | Type | Description | Required | |-----|------|-------------|----------| | `filePath` | string | Absolute or relative path to the `.mjs` schema file | Yes | | `listsDir` | string | Directory containing shared list files | No | | `allowlist` | array | Allowed library names for handlers. Uses default if omitted | No | **Example** ```javascript import { FlowMCP } from 'flowmcp-core' const { status, main, handlerMap } = await FlowMCP.loadSchema( { filePath: './schemas/coingecko-price.mjs' } ) if( !status ) { console.error( 'Schema loading failed' ) } // Use the loaded schema const result = await FlowMCP.fetch( { main, handlerMap, userParams: { id: 'bitcoin' }, serverParams: {}, routeName: 'getPrice' } ) ``` **Returns** ```javascript { status: true, // false if loading, validation, or security scan failed main: { ... }, // The validated main export object (with tools, resources, skills) handlerMap: { ... } // Tool-keyed handler functions (empty object if no handlers) } ``` ### .validateMain() Validates a `main` export object against the FlowMCP specification. Runs validation rules across categories including structure, naming, parameters, security, output declarations, resources, and skills. **Method** ```javascript const { status, messages } = FlowMCP.validateMain( { main } ) ``` **Parameters** | Key | Type | Description | Required | |-----|------|-------------|----------| | `main` | object | The `main` export from a schema file. Accepts both `tools` and `routes` (deprecated alias) | Yes | **Example** ```javascript import { FlowMCP } from 'flowmcp-core' import { main } from './schemas/coingecko-price.mjs' const { status, messages } = FlowMCP.validateMain( { main } ) if( status ) { console.log( 'Schema is valid' ) } else { console.error( 'Validation failed:' ) messages.forEach( ( msg ) => console.error( ` - ${msg}` ) ) } ``` **Returns** ```javascript { status: true, // true if all rules pass messages: [] // Array of error messages when status is false } ``` :::tip Use `validateMain()` during development to catch schema errors early. In production, use `loadSchema()` which includes validation as part of the full pipeline. ::: ### .scanSecurity() Runs a static security scan on a schema file. Checks for forbidden patterns like `import` statements, `require()` calls, filesystem access, `eval()`, and other disallowed constructs. **Method** ```javascript const { status, messages } = await FlowMCP.scanSecurity( { filePath } ) ``` **Parameters** | Key | Type | Description | Required | |-----|------|-------------|----------| | `filePath` | string | Path to the `.mjs` schema file to scan | Yes | **Example** ```javascript import { FlowMCP } from 'flowmcp-core' const { status, messages } = await FlowMCP.scanSecurity( { filePath: './schemas/my-schema.mjs' } ) if( !status ) { console.error( 'Security violations found:' ) messages.forEach( ( msg ) => console.error( ` - ${msg}` ) ) } ``` **Returns** ```javascript { status: true, // false if forbidden patterns are detected messages: [] // Descriptions of security violations } ``` --- ## Execution ### .fetch() Executes an HTTP request for a specific tool using the loaded schema. Handles parameter substitution, URL construction, header injection, and optional pre/post-processing via handlers. **Method** ```javascript const result = await FlowMCP.fetch( { main, handlerMap, userParams, serverParams, routeName } ) ``` **Parameters** | Key | Type | Description | Required | |-----|------|-------------|----------| | `main` | object | The validated `main` export from a schema | Yes | | `handlerMap` | object | Handler map from `loadSchema()` or `createHandlers()` | Yes | | `userParams` | object | User-provided parameters (from AI client input) | Yes | | `serverParams` | object | Server-side parameters (API keys, tokens) | Yes | | `routeName` | string | Name of the tool to execute | Yes | **Example** ```javascript import { FlowMCP } from 'flowmcp-core' const { status, main, handlerMap } = await FlowMCP.loadSchema( { filePath: './schemas/coingecko-price.mjs' } ) const result = await FlowMCP.fetch( { main, handlerMap, userParams: { id: 'bitcoin', vs_currency: 'usd' }, serverParams: {}, routeName: 'getPrice' } ) if( result.status ) { console.log( 'Response:', result.dataAsString ) } else { console.error( 'Request failed:', result.messages ) } ``` **Returns** ```javascript { status: true, // false if request failed dataAsString: '{"bitcoin":{"usd":45000}}', // Response body as string messages: [] // Error messages when status is false } ``` :::caution The `serverParams` object should contain API keys and secrets. These values are injected into headers and parameters at runtime but are never exposed to AI clients. ::: --- ## Shared Lists & Dependencies ### .resolveSharedLists() Resolves shared list references from a directory of list files. Shared lists are reusable value collections (chain IDs, token symbols, protocol names) that schemas reference via `$listName` syntax in enum parameters. **Method** ```javascript const { sharedLists } = await FlowMCP.resolveSharedLists( { sharedListRefs, listsDir } ) ``` **Parameters** | Key | Type | Description | Required | |-----|------|-------------|----------| | `sharedListRefs` | array | Array of shared list reference strings from the schema | Yes | | `listsDir` | string | Directory path containing shared list `.mjs` files | Yes | **Example** ```javascript import { FlowMCP } from 'flowmcp-core' const { sharedLists } = await FlowMCP.resolveSharedLists( { sharedListRefs: [ 'evmChains', 'stablecoins' ], listsDir: './lists/' } ) console.log( 'Resolved lists:', Object.keys( sharedLists ) ) // Output: ['evmChains', 'stablecoins'] ``` **Returns** ```javascript { sharedLists: { evmChains: [ 'ethereum', 'polygon', 'arbitrum', ... ], stablecoins: [ 'USDT', 'USDC', 'DAI', ... ] } } ``` ### .interpolateEnum() Interpolates shared list values into an enum template string. Replaces `$listName` references with actual values from resolved shared lists. **Method** ```javascript const { result } = FlowMCP.interpolateEnum( { template, sharedLists } ) ``` **Parameters** | Key | Type | Description | Required | |-----|------|-------------|----------| | `template` | string | Enum template containing `$listName` references | Yes | | `sharedLists` | object | Resolved shared lists from `resolveSharedLists()` | Yes | **Example** ```javascript import { FlowMCP } from 'flowmcp-core' const sharedLists = { evmChains: [ 'ethereum', 'polygon', 'arbitrum' ] } const { result } = FlowMCP.interpolateEnum( { template: '$evmChains', sharedLists } ) console.log( result ) // Output: ['ethereum', 'polygon', 'arbitrum'] ``` **Returns** ```javascript { result: [ 'ethereum', 'polygon', 'arbitrum' ] // Resolved enum values } ``` ### .loadLibraries() Loads npm packages declared in a schema's `requiredLibraries` field. Only packages on the allowlist can be loaded. This enforces the zero-import security model. **Method** ```javascript const { libraries } = await FlowMCP.loadLibraries( { requiredLibraries, allowlist } ) ``` **Parameters** | Key | Type | Description | Required | |-----|------|-------------|----------| | `requiredLibraries` | array | Library names declared in the schema | Yes | | `allowlist` | array | Permitted library names. Use `getDefaultAllowlist()` for defaults | Yes | **Example** ```javascript import { FlowMCP } from 'flowmcp-core' const { allowlist } = FlowMCP.getDefaultAllowlist() const { libraries } = await FlowMCP.loadLibraries( { requiredLibraries: [ 'ethers' ], allowlist } ) // libraries.ethers is now available for handler injection ``` **Returns** ```javascript { libraries: { ethers: { ... } // The loaded library module } } ``` ### .getDefaultAllowlist() Returns the default library allowlist. These are the npm packages that handlers are permitted to use via dependency injection. **Method** ```javascript const { allowlist } = FlowMCP.getDefaultAllowlist() ``` **Parameters** None. **Example** ```javascript import { FlowMCP } from 'flowmcp-core' const { allowlist } = FlowMCP.getDefaultAllowlist() console.log( 'Allowed libraries:', allowlist ) ``` **Returns** ```javascript { allowlist: [ 'ethers', 'viem', ... ] // Array of permitted library names } ``` --- ## Handler Management ### .createHandlers() Creates a handler map by invoking the `handlers` factory function with injected dependencies. The resulting map is keyed by tool name and contains `preProcess` and `postProcess` functions. **Method** ```javascript const { handlerMap } = FlowMCP.createHandlers( { handlersFn, sharedLists, libraries, routeNames } ) ``` **Parameters** | Key | Type | Description | Required | |-----|------|-------------|----------| | `handlersFn` | function | The `handlers` factory function from a schema | Yes | | `sharedLists` | object | Resolved shared lists to inject | Yes | | `libraries` | object | Loaded libraries to inject | Yes | | `routeNames` | array | Expected tool names for validation | Yes | **Example** ```javascript import { FlowMCP } from 'flowmcp-core' import { handlers } from './schemas/my-schema.mjs' const { handlerMap } = FlowMCP.createHandlers( { handlersFn: handlers, sharedLists: { evmChains: [ 'ethereum', 'polygon' ] }, libraries: {}, routeNames: [ 'getPrice', 'getHistory' ] } ) // handlerMap.getPrice.postProcess is now available ``` **Returns** ```javascript { handlerMap: { getPrice: { postProcess: async ( { data } ) => { ... } }, getHistory: { preProcess: async ( { params } ) => { ... }, postProcess: async ( { data } ) => { ... } } } } ``` :::tip You rarely need to call `createHandlers()` directly. The `loadSchema()` pipeline handles handler creation automatically. Use this method when you need manual control over the dependency injection process. ::: --- ## Legacy Compatibility ### .detectLegacy() Detects whether a loaded module uses the v1 schema format. Returns the detected format version. **Method** ```javascript const { isLegacy, format } = FlowMCP.detectLegacy( { module } ) ``` **Parameters** | Key | Type | Description | Required | |-----|------|-------------|----------| | `module` | object | The imported module from a `.mjs` schema file | Yes | **Example** ```javascript import { FlowMCP } from 'flowmcp-core' const schemaModule = await import( './schemas/old-schema.mjs' ) const { isLegacy, format } = FlowMCP.detectLegacy( { module: schemaModule } ) if( isLegacy ) { console.log( `Legacy format detected: ${format}` ) // Use adaptLegacy() to convert } ``` **Returns** ```javascript { isLegacy: true, // true if the module uses v1 format format: 'v1' // Detected format version string } ``` ### .adaptLegacy() Converts a v1 schema object to the v2 two-export format. Returns the adapted `main` export, optional handlers factory function, and any conversion warnings. **Method** ```javascript const { main, handlersFn, hasHandlers, warnings } = FlowMCP.adaptLegacy( { legacySchema } ) ``` **Parameters** | Key | Type | Description | Required | |-----|------|-------------|----------| | `legacySchema` | object | A v1 format schema object | Yes | **Example** ```javascript import { FlowMCP } from 'flowmcp-core' const oldSchema = { namespace: 'myapi', root: '...', routes: { ... } } const { main, handlersFn, hasHandlers, warnings } = FlowMCP.adaptLegacy( { legacySchema: oldSchema } ) if( warnings.length > 0 ) { console.log( 'Migration warnings:' ) warnings.forEach( ( w ) => console.log( ` - ${w}` ) ) } // Use the adapted schema with current methods const result = await FlowMCP.fetch( { main, handlerMap: {}, userParams: { ... }, serverParams: {}, routeName: 'myRoute' } ) ``` **Returns** ```javascript { main: { ... }, // Converted main export handlersFn: Function|null, // Handlers factory (null if no handlers) hasHandlers: false, // Whether the schema had handlers warnings: [] // Conversion warnings (deprecated features, etc.) } ``` --- ## Output Schema Generation ### .generateOutputSchema() Generates an output schema from a captured API response. The output schema declares the expected response shape for downstream consumers and documentation. **Method** ```javascript const { output } = FlowMCP.generateOutputSchema( { response, mimeType } ) ``` **Parameters** | Key | Type | Description | Required | |-----|------|-------------|----------| | `response` | string | Raw API response body | Yes | | `mimeType` | string | Response MIME type (e.g. `application/json`) | Yes | **Example** ```javascript import { FlowMCP } from 'flowmcp-core' const { output } = FlowMCP.generateOutputSchema( { response: '{"bitcoin":{"usd":45000,"eur":38000}}', mimeType: 'application/json' } ) console.log( output ) // { type: 'object', fields: { bitcoin: { type: 'object', fields: { ... } } } } ``` **Returns** ```javascript { output: { type: 'object', fields: { ... } // Inferred field structure from the response } } ``` :::tip Use this method during schema development to auto-generate the `output` block for your tools. Capture a real API response with `fetch()`, then pass it to `generateOutputSchema()`. ::: --- ## v1 API (Legacy) The v1 API is still available for backward compatibility. Import it separately: ```javascript import { v1 } from 'flowmcp-core' const { FlowMCP } = v1 ```
v1 Method Overview The v1 API uses a flat schema format (single export) with different method signatures. | Method | v1 Signature | Current Equivalent | |--------|-------------|---------------| | `.validateSchema()` | `FlowMCP.validateSchema( { schema } )` | `.validateMain( { main } )` | | `.fetch()` | `FlowMCP.fetch( { schema, userParams, serverParams, routeName } )` | `.fetch( { main, handlerMap, ... } )` | | `.activateServerTools()` | `FlowMCP.activateServerTools( { server, schema, serverParams } )` | Use MCP SDK directly with `.loadSchema()` | | `.activateServerTool()` | `FlowMCP.activateServerTool( { server, schema, routeName, serverParams } )` | Use MCP SDK directly | | `.prepareServerTool()` | `FlowMCP.prepareServerTool( { schema, serverParams, routeName } )` | Use `.loadSchema()` + `.fetch()` | | `.filterArrayOfSchemas()` | `FlowMCP.filterArrayOfSchemas( { arrayOfSchemas, ... } )` | Same (v1 only) | | `.getArgvParameters()` | `FlowMCP.getArgvParameters( { argv } )` | Same (v1 only) | | `.getZodInterfaces()` | `FlowMCP.getZodInterfaces( { schema } )` | Zod schemas are generated during `.loadSchema()` | | `.getAllTests()` | `FlowMCP.getAllTests( { schema } )` | Test values are in parameter `test` fields | :::caution The v1 API will be maintained for backward compatibility but will not receive new features. All new schemas should use the v3 format. :::
--- ## Typical Workflow The standard workflow for using FlowMCP Core combines these methods: ```javascript import { FlowMCP } from 'flowmcp-core' // 1. Load schema (validates, scans security, resolves lists, creates handlers) const { status, main, handlerMap } = await FlowMCP.loadSchema( { filePath: './schemas/coingecko-price.mjs' } ) if( !status ) { throw new Error( 'Schema loading failed' ) } // 2. Execute a tool const result = await FlowMCP.fetch( { main, handlerMap, userParams: { id: 'bitcoin' }, serverParams: { API_KEY: process.env.COINGECKO_KEY }, routeName: 'getPrice' } ) // 3. Use the result if( result.status ) { console.log( 'Price data:', result.dataAsString ) } else { console.error( 'Errors:', result.messages ) } ``` For MCP server integration, see the Server Integration Guide. --- # Troubleshooting /docs/reference/troubleshooting Solutions to common issues when working with FlowMCP schemas, servers, and integrations. ## Schema Validation Errors
Namespace format errors **Error:** `namespace must contain only lowercase letters` The `namespace` field accepts only lowercase `a-z` characters. No numbers, hyphens, underscores, or uppercase. ```javascript // Wrong namespace: 'github-api' // no hyphens namespace: 'api2' // no numbers namespace: 'CoinGecko' // no uppercase // Correct namespace: 'github' namespace: 'coingecko' ```
Version field errors **Error:** `version must be a valid semver string` The `version` field must be a full semver string without prefix. ```javascript // Wrong version: '2.0' // missing patch version: 'v2.0.0' // no 'v' prefix version: '2' // not semver // Correct version: '2.0.0' ```
Parameter structure errors **Error:** `parameter must have position and z fields` Every parameter in v2.0.0 requires a `position` block and a `z` block. ```javascript // Wrong - missing z block parameters: [ { position: { key: 'id', value: '{{ID}}', location: 'insert' } } ] // Correct parameters: [ { position: { key: 'id', value: '{{ID}}', location: 'insert' }, z: { primitive: 'string()', options: [] } } ] ```
Route limit exceeded **Error:** `schema exceeds maximum of 8 routes` v2.0.0 limits schemas to a maximum of 8 routes. Split large schemas into multiple files grouped by endpoint type. ```javascript // Wrong - too many routes in one schema routes: { route1: { ... }, route2: { ... }, route3: { ... }, route4: { ... }, route5: { ... }, route6: { ... }, route7: { ... }, route8: { ... }, route9: { ... } // 9th route fails } // Correct - split into separate schema files // coingecko-price.mjs (3 routes) // coingecko-market.mjs (4 routes) // coingecko-info.mjs (2 routes) ```
Missing required fields **Error:** `main.root is required` or `main.requiredServerParams is required` v2.0.0 requires several fields that were optional in v1. Check the full list: ```javascript export const main = { namespace: 'provider', // Required name: 'Display Name', // Required description: 'What it does', // Required version: '2.0.0', // Required root: 'https://api.example.com', // Required requiredServerParams: [], // Required (empty array if none) requiredLibraries: [], // Required (empty array if none) headers: {}, // Required (empty object if none) routes: { ... } // Required (at least one route) } ```
## Server Startup Issues
Port already in use **Error:** `EADDRINUSE: address already in use :::3000` Another process is using the port. Find and stop it: ```bash # Find what is using port 3000 lsof -i :3000 # Kill the process by PID kill # Or use a different port PORT=3001 node server.mjs ```
Missing environment variables **Error:** `Required server parameter API_KEY is not set` The schema declares `requiredServerParams` that must be present in the environment. ```bash # Check if variable is set echo $API_KEY # Set the variable export API_KEY=your_key_here # Or pass inline API_KEY=your_key_here node server.mjs ``` :::tip Never commit API keys to git. Use `.env` files (with `.gitignore`) or environment variables set in your shell profile. :::
MCP SDK version mismatch **Error:** `Cannot find module '@modelcontextprotocol/sdk'` or unexpected API errors Ensure you are using a compatible MCP SDK version: ```bash # Check installed version npm ls @modelcontextprotocol/sdk # Install latest npm install @modelcontextprotocol/sdk@latest ```
Schema file not found **Error:** `ENOENT: no such file or directory` Verify the schema path is correct and the file exists: ```bash # Check if file exists ls -la ./schemas/my-schema.mjs # Use absolute path if relative fails node -e "console.log(require('path').resolve('./schemas/my-schema.mjs'))" ```
## API Request Failures
401 Unauthorized The API rejected your authentication credentials. **Common causes:** - API key expired or revoked - Wrong header format (Bearer vs. token vs. API key) - Key set in wrong `serverParams` field ```javascript // Check your header template matches the API docs headers: { 'Authorization': 'Bearer {{API_KEY}}' // Bearer token 'X-API-Key': '{{API_KEY}}' // API key header 'Authorization': 'token {{GITHUB_TOKEN}}' // GitHub format } // Ensure serverParams match requiredServerParams requiredServerParams: [ 'API_KEY' ], // When running: API_KEY=xxx node server.mjs ```
429 Rate Limited The API is throttling your requests. **Solutions:** - Check the API documentation for rate limit quotas - Add delays between requests in batch operations - Use a paid API tier for higher limits - Cache responses when possible
Request timeout The API did not respond within the timeout window. **Solutions:** - Verify the API endpoint is accessible: `curl -I https://api.example.com` - Check network connectivity - Some APIs have slow endpoints for large data sets -- consider pagination parameters
Invalid JSON response **Error:** `Unexpected token < in JSON at position 0` The API returned HTML or XML instead of JSON. Common causes: - Wrong base URL (hitting a web page instead of API) - API requires authentication and returns an HTML login page - API endpoint has changed ```javascript // Verify root URL points to the API, not the website root: 'https://api.example.com' // Correct root: 'https://www.example.com' // Wrong - website, not API ```
## Claude Desktop Integration
MCP server not showing in Claude 1. Verify your config file location: - **macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json` - **Windows:** `%APPDATA%\Claude\claude_desktop_config.json` 2. Check config format: ```json { "mcpServers": { "flowmcp": { "command": "node", "args": ["/absolute/path/to/server.mjs"], "env": { "API_KEY": "your_key_here" } } } } ``` 3. Use **absolute paths** in `args` -- relative paths may not resolve correctly 4. Restart Claude Desktop completely (quit and reopen, not just close the window)
Tools not appearing after server starts - Verify schemas load without errors by running the server manually in a terminal - Check that `loadSchema()` returns `status: true` for all schemas - Confirm tools are registered using the MCP SDK's tool listing - Check Claude Desktop logs for MCP-related errors
Tool calls returning errors - Test the same route with `FlowMCP.fetch()` outside Claude to isolate the issue - Check that all `requiredServerParams` are set in the Claude Desktop config `env` block - Verify the API endpoint is reachable from your machine
## Handler Issues (v2.0.0)
Handler factory function errors **Error:** `handlers export must be a function` The `handlers` export must be a factory function, not a plain object. ```javascript // Wrong - plain object export const handlers = { routeName: { postProcess: ( { data } ) => data } } // Correct - factory function export const handlers = ( { sharedLists, libraries } ) => ({ routeName: { postProcess: ( { data } ) => { return data } } }) ```
Dependency injection errors **Error:** `Library 'ethers' is not on the allowlist` Libraries must be declared in `requiredLibraries` in the `main` export. Only allowlisted libraries can be injected. ```javascript export const main = { // ... requiredLibraries: [ 'ethers' ], // Declare here // ... } export const handlers = ( { libraries } ) => ({ routeName: { postProcess: ( { data } ) => { const ethers = libraries.ethers // Available via injection return ethers.formatUnits( data.value, 18 ) } } }) ```
Shared list not found **Error:** `Shared list 'evmChains' not found in lists directory` The schema references a shared list that does not exist. - Verify the list file exists in the lists directory (e.g. `lists/evmChains.mjs`) - Check the list name matches exactly (case-sensitive) - Ensure `listsDir` is passed to `loadSchema()` if using shared lists
Handler not matching route name **Error:** `Handler key 'getprice' does not match any route name` Handler keys must exactly match route names from `main.routes`. ```javascript // main.routes has: routes: { getPrice: { ... } } // Wrong handler key export const handlers = ( deps ) => ({ getprice: { ... } // lowercase 'p' does not match }) // Correct handler key export const handlers = ( deps ) => ({ getPrice: { ... } // matches routes.getPrice }) ```
## Error Messages Reference ### Schema Errors | Error | Cause | Solution | |-------|-------|----------| | `namespace invalid` | Contains non-lowercase-letter characters | Use only `a-z` | | `route missing method` | Route has no `method` field | Add `method: 'GET'` (or POST, etc.) | | `parameter missing z` | Parameter lacks Zod validation | Add `z` block to parameter | | `serverParams not declared` | Header uses `{{KEY}}` but `KEY` not in `requiredServerParams` | Add to `requiredServerParams` array | | `routes exceed max count` | More than 8 routes in one schema | Split into multiple schema files | | `handlers is not a function` | `handlers` exported as object | Convert to factory function | ### Runtime Errors | Error | Cause | Solution | |-------|-------|----------| | `ECONNREFUSED` | Target server not running | Verify API endpoint is accessible | | `ETIMEDOUT` | Request timeout | Check network, increase timeout | | `ENOTFOUND` | Invalid hostname in URL | Verify `root` URL in schema | | `Invalid JSON` | Response is not valid JSON | Check API endpoint returns JSON | | `Required server parameter missing` | `serverParams` value not set | Set the environment variable | ## Debug Checklist Before reporting an issue, verify the following: 1. **Schema validates** -- Run `FlowMCP.validateMain( { main } )` and check for errors 2. **Security scan passes** -- Run `FlowMCP.scanSecurity( { filePath } )` on the schema file 3. **Schema loads** -- Run `FlowMCP.loadSchema()` and verify `status: true` 4. **Environment variables set** -- All `requiredServerParams` values are available 5. **API reachable** -- Test the API endpoint directly with `curl` 6. **Node.js version** -- Verify Node.js 22+ is installed (`node --version`) 7. **Dependencies installed** -- Run `npm ci` to ensure clean install 8. **Test with simple schema** -- Try a minimal schema with one route and no handlers ## Getting Help :::note[GitHub Issues] Bug reports and feature requests: github.com/FlowMCP/flowmcp-core/issues (https://github.com/FlowMCP/flowmcp-core/issues) ::: :::note[GitHub Discussions] Questions, ideas, and community help: github.com/FlowMCP/flowmcp-core/discussions (https://github.com/FlowMCP/flowmcp-core/discussions) ::: ### Issue Report Template When reporting issues, include: ```markdown **Environment:** - FlowMCP Core version: x.x.x - Node.js version: xx.x.x - OS: macOS / Windows / Linux **Schema (minimal reproduction):** export const main = { // Minimal schema that reproduces the issue } **Error:** // Full error message and stack trace **Expected behavior:** What should happen **Actual behavior:** What actually happens ``` --- # Overview /docs/schemas/overview A schema is a single `.mjs` file that wraps a data source for AI agents. Each schema declares its tools, resources, prompts, and skills in a static `main` export. An optional `handlers` export adds response transformation. FlowMCP v3.0.0 supports four primitives: :::note[Tools] REST API endpoints. Map parameters to URLs, inject authentication, validate inputs. The core primitive — every schema has at least one tool. See Tools. ::: :::note[Resources] Local SQLite databases. Fast, deterministic queries for bulk data like company registers, transit schedules, or sanctions lists. See Resources. ::: :::note[Prompts] Explanatory texts that teach AI agents how a provider's tools work together — pagination patterns, error codes, data interpretation. See Prompts. ::: :::note[Skills] Multi-step workflow instructions. Reusable pipelines that compose tools and resources into higher-level operations. See Skills. ::: ## Schema Structure Every schema exports two things: | Export | Required | Purpose | |--------|----------|---------| | `main` | Yes | Declarative configuration — tools, resources, prompts, skills. JSON-serializable and hashable. | | `handlers` | No | Response transformation — pre/post processing for API responses. | :::tip Most schemas only need `main`. Add `handlers` when API responses need transformation before reaching the AI agent. ::: --- # Prompts /docs/schemas/prompts Prompts are explanatory texts scoped to a namespace. They teach AI agents how a provider's tools work together -- pagination patterns, error codes, rate limits, combining endpoints. Prompts **explain**, they don't **instruct** (that's what Skills do). ## Prompts vs Skills | Aspect | Prompts | Skills | |--------|---------|--------| | Purpose | Explain how tools work | Instruct step-by-step workflows | | Tone | "Here's how pagination works..." | "Step 1: Call X. Step 2: Pass result to Y." | | Model dependency | Model-neutral | Model-specific (`testedWith` required) | | Scope | Single namespace | Can reference tools from own schema | See Prompt Architecture for the full two-tier system (Provider-Prompts vs Agent-Prompts). ## Defining Prompts Prompts are declared in `main.prompts` and loaded from external `.mjs` files via `contentFile`: ```javascript export const main = { namespace: 'coingecko', // ... other fields ... prompts: { 'about': { contentFile: './prompts/about.mjs' }, 'pagination-guide': { contentFile: './prompts/pagination-guide.mjs' } } } ``` ## Prompt File Format Each prompt is a `.mjs` file exporting a `prompt` object: ```javascript // prompts/about.mjs export const prompt = { name: 'about', version: 'flowmcp-prompt/1.0.0', provider: 'coingecko', description: 'Overview of CoinGecko API capabilities and best practices', dependsOn: ['coingecko.simplePrice', 'coingecko.coinMarkets', 'coingecko.coinMarketChart'], references: [], content: `CoinGecko provides cryptocurrency market data through three main tools: Use {{tool:simplePrice}} for current prices of one or more tokens. Use {{tool:coinMarkets}} for market cap rankings with sorting and pagination. Use {{tool:coinMarketChart}} for historical price data over {{input:days}} days. All price endpoints return values in the currency specified by {{input:vsCurrency}}. Rate limit: 30 requests per minute on the free tier.` } ``` ## Placeholder Syntax Use `{{type:name}}` placeholders in the `content` field to reference schema elements: | Placeholder | Resolves To | Example | |-------------|-------------|---------| | `{{tool:name}}` | A tool in the same namespace | `{{tool:simplePrice}}` | | `{{input:key}}` | A user input parameter | `{{input:tokenId}}` | | `{{resource:name}}` | A resource in the same namespace | `{{resource:companiesDb}}` | :::note **The `about` convention** -- Every provider should have an `about` prompt that describes the overall API capabilities, common patterns, and gotchas. This is the first thing an AI agent reads when encountering a new namespace. ::: ## Complete Example A CoinGecko schema with an `about` prompt: ```javascript // coingecko-coins.mjs export const main = { namespace: 'coingecko', name: 'CoinData', version: '3.0.0', root: 'https://api.coingecko.com/api/v3', tools: { simplePrice: { /* ... */ }, coinMarkets: { /* ... */ }, coinMarketChart: { /* ... */ } }, prompts: { 'about': { contentFile: './prompts/about.mjs' } } } ``` ```javascript // prompts/about.mjs export const prompt = { name: 'about', version: 'flowmcp-prompt/1.0.0', provider: 'coingecko', description: 'How to use CoinGecko tools effectively', dependsOn: ['coingecko.simplePrice', 'coingecko.coinMarkets', 'coingecko.coinMarketChart'], references: [], content: `CoinGecko provides real-time and historical cryptocurrency data. {{tool:simplePrice}} returns current prices. Pass comma-separated token IDs. {{tool:coinMarkets}} returns paginated market data sorted by market cap. - Use page and per_page parameters for pagination (max 250 per page). - Default sort is market_cap_desc. {{tool:coinMarketChart}} returns OHLC + volume over {{input:days}} days. - Granularity is automatic: 1-2 days = 5min, 3-30 days = hourly, 31+ = daily. All endpoints accept {{input:vsCurrency}} (e.g. "usd", "eur", "btc").` } ``` --- # Resources /docs/schemas/resources Resources provide local, deterministic data via SQLite. Unlike tools (which call remote REST APIs), resources query local databases. They are perfect for bulk-downloaded open data such as company registers, transit schedules, and sanctions lists. ## What are Resources? A resource is a SQLite database bundled with a schema. The FlowMCP runtime loads the `.db` file via `sql.js` (a pure JavaScript/WASM SQLite implementation) and exposes each defined query as an MCP resource. No network calls, no API keys, no rate limits. Resources are ideal when: - Data is large and rarely changes (company registers, geographic data) - Offline access is required - Latency must be near-zero - The dataset is publicly available as a bulk download ## When to Use Resources vs Tools | Aspect | Tools | Resources | |--------|-------|-----------| | Data source | Remote REST API | Local SQLite database | | Latency | Network-dependent | Instant | | Availability | Requires internet | Always available | | Data freshness | Real-time | Snapshot (periodic refresh) | | API key required | Usually yes | No | | Use case | Live prices, on-chain data | Company registers, transit data | :::tip Many schemas benefit from combining both. Use tools for live data and resources for reference lookups. The AI agent chooses the right approach based on the query. ::: ## Resource Definition Resources are declared inside `main.resources`. Each resource points to a SQLite database and defines named queries with SQL and parameters: ```javascript export const main = { namespace: 'offeneregister', name: 'OffeneRegister', version: '3.0.0', root: '', tools: {}, resources: { companiesDb: { source: 'sqlite', database: 'companies.db', origin: 'global', description: 'German company register (OffeneRegister)', queries: { searchCompanies: { sql: "SELECT * FROM companies WHERE name LIKE ? LIMIT ?", description: 'Search companies by name', parameters: { searchTerm: { type: 'string', required: true }, limit: { type: 'number', required: false, default: 10 } }, output: { columns: ['company_number', 'name', 'registered_address', 'status'] } } } } } } ``` ## Database Paths -- Three Levels The `origin` field determines where the runtime looks for the `.db` file: | Origin | Path Resolution | Best For | |--------|----------------|----------| | `global` | `~/.flowmcp/data/{database}` | Shared datasets used across projects | | `project` | `.flowmcp/data/{database}` | Project-specific data | | `inline` | Relative to the schema file | Self-contained schemas with small databases | :::note The `origin` field is required. The runtime does not guess where the database lives. If the file is not found at the resolved path, the resource fails to load with a clear error message. ::: ## CTE Support Complex queries can use Common Table Expressions (CTEs) for multi-step filtering: ```sql WITH recent AS ( SELECT * FROM companies WHERE registered_date > ? ) SELECT * FROM recent WHERE status = 'active' LIMIT ? ``` CTEs must still start with a read-only statement. The same SQL security rules apply: no `INSERT`, `UPDATE`, `DELETE`, or other write operations anywhere in the CTE chain. ## Constraints :::note These limits keep resources focused and predictable. If you need more queries, split into multiple schemas. ::: | Constraint | Value | Rationale | |------------|-------|-----------| | Max resources per schema | 2 | Resources are supplementary, not primary output | | Max queries per resource | 8 | 7 defined + 1 auto-injected `freeQuery` | | `getSchema` query | Required | Must return the database table structure | | SQL operations | `SELECT` only | Read-only enforcement -- no INSERT/UPDATE/DELETE | | Parameter placeholders | `?` only | Prevents SQL injection | | Source type | `sqlite` only | Future versions may add other sources | | Database file extension | `.db` | Standard SQLite extension | ### Auto-Injected Queries Two queries are handled automatically by the runtime: - **`getSchema`** -- You must define this query. It returns the database structure so AI agents can understand available tables and columns. - **`freeQuery`** -- Auto-injected by the runtime. Allows AI agents to run arbitrary `SELECT` queries within the read-only sandbox. This counts toward the 8-query limit. ## Complete Example An OffeneRegister schema with a SQLite resource for querying the German company register: ```javascript export const main = { namespace: 'offeneregister', name: 'OffeneRegister', description: 'German company register — local SQLite database', version: '3.0.0', tags: ['open-data', 'germany', 'companies'], root: '', tools: {}, resources: { companiesDb: { source: 'sqlite', database: 'openregister.db', origin: 'global', description: 'OffeneRegister company database (2.5 GB)', queries: { getSchema: { sql: "SELECT sql FROM sqlite_master WHERE type='table'", description: 'Get database schema', parameters: {}, output: { columns: ['sql'] } }, searchCompanies: { sql: "SELECT company_number, name, registered_address, status FROM companies WHERE name LIKE ? LIMIT ?", description: 'Full-text search for companies by name', parameters: { searchTerm: { type: 'string', required: true, description: 'Company name (use % for wildcards)' }, limit: { type: 'number', required: false, default: 10, description: 'Max results' } }, output: { columns: ['company_number', 'name', 'registered_address', 'status'] } }, getByNumber: { sql: "SELECT * FROM companies WHERE company_number = ?", description: 'Look up a company by its registration number', parameters: { companyNumber: { type: 'string', required: true, description: 'Company registration number' } }, output: { columns: ['company_number', 'name', 'registered_address', 'status', 'registered_date'] } }, recentRegistrations: { sql: "SELECT company_number, name, registered_date FROM companies ORDER BY registered_date DESC LIMIT ?", description: 'List the most recently registered companies', parameters: { limit: { type: 'number', required: false, default: 20, description: 'Max results' } }, output: { columns: ['company_number', 'name', 'registered_date'] } } } } } } ``` This schema has no tools and no `root` URL -- it operates entirely on local data. The AI agent can search companies, look up by number, or browse recent registrations without any network access. :::tip Resource-only schemas set `root: ''` and `tools: {}`. They are valid FlowMCP schemas that expose only MCP resources, no MCP tools. ::: ## Validation Rules Resources are validated by rules RES001-RES023. Key rules include: | Code | Rule | |------|------| | RES003 | Maximum 2 resources per schema | | RES005 | Source must be `'sqlite'` | | RES006 | Database path must end in `.db` | | RES008 | Maximum 8 queries per resource (7 + freeQuery) | | RES012 | SQL must start with `SELECT` (or `With` for CTEs) | | RES013 | SQL must not contain blocked patterns | | RES014 | SQL must use `?` placeholders | | RES015 | Placeholder count must match parameter count | See Validation Rules for the complete list. --- # Skills /docs/schemas/skills Skills are instructional multi-step workflows embedded in a schema. Unlike Prompts (which explain context), Skills **instruct** -- they tell an LLM exactly what to do, step by step. Each skill declares its tool dependencies, defines typed input parameters, and records the model it was tested with. :::tip **Skills vs Prompts:** Use a Prompt when you want to provide context or explain a domain. Use a Skill when you want to orchestrate a repeatable multi-step workflow that calls specific tools in sequence. ::: ## Skill Execution Flow How a skill is resolved and executed at runtime: The skill's `content` is a template. Placeholders like `{{input:tokenSymbol}}` are replaced with user-provided values, and `{{tool:computeRSI}}` references tell the LLM which tool to call at each step. ## Defining Skills Skills are declared in the `main` export's `skills` key. Each entry points to a separate `.mjs` file: ```javascript export const main = { namespace: 'tradingsignals', name: 'TradingSignals', version: '3.0.0', root: 'https://api.example.com', tools: { computeRSI: { /* ... */ }, computeMACD: { /* ... */ } }, skills: { 'token-technical-analysis': { file: './skills/token-technical-analysis.mjs' } } } ``` ### Skill Reference Fields Each key in `main.skills` is the skill name (must match `^[a-z][a-z0-9-]*$`). The value is an object: | Field | Type | Required | Description | |-------|------|----------|-------------| | `file` | `string` | Yes | Path to the `.mjs` skill file, relative to the schema. Must end in `.mjs`. | ## Skill File Format Each skill file exports a `skill` object with the full workflow definition: ```javascript const content = `Perform technical analysis of {{input:tokenSymbol}} on {{input:chain}}. ## Step 1: Token Discovery Use {{tool:searchBySymbol}} to find {{input:tokenSymbol}} on {{input:chain}}. ## Step 2: OHLCV Data Use {{tool:getRecursiveOhlcvEVM}} for {{input:timeframeDays}}-day candles. ## Step 3: Indicators Use {{tool:computeRSI}} with period 14. Use {{tool:computeMACD}} with fast 12, slow 26, signal 9. ## Step 4: Synthesis Compile findings into BULLISH / BEARISH / NEUTRAL signal.` export const skill = { name: 'token-technical-analysis', version: 'flowmcp-skill/1.0.0', description: 'Full technical analysis: discovery, OHLCV, indicators, chart, signal summary', testedWith: 'anthropic/claude-sonnet-4-5-20250929', requires: { tools: [ 'indicators/tool/searchBySymbol', 'ohlcv/tool/getRecursiveOhlcvEVM', 'tradingsignals/tool/computeRSI', 'tradingsignals/tool/computeMACD' ], resources: [], external: [ 'playwright' ] }, input: [ { key: 'tokenSymbol', type: 'string', description: 'Token symbol (e.g. WETH)', required: true }, { key: 'chain', type: 'string', description: 'Blockchain (e.g. ethereum)', required: true }, { key: 'timeframeDays', type: 'number', description: 'Days of history', required: false } ], output: 'Structured report with trend, momentum, volatility, chart, and signal summary', content } ``` ## Skill Object Fields | Field | Type | Required | Description | |-------|------|----------|-------------| | `name` | `string` | Yes | Must match the `name` in the schema's `skills` array entry. | | `version` | `string` | Yes | Must be `'flowmcp-skill/1.0.0'`. | | `description` | `string` | Yes | What this skill does. | | `testedWith` | `string` | Yes | Model ID the skill was tested with (e.g. `anthropic/claude-sonnet-4-5-20250929`). | | `requires` | `object` | Yes | Dependencies: `tools`, `resources`, and `external` arrays. | | `input` | `array` | No | User-provided input parameters. | | `output` | `string` | No | Description of what the skill produces. | | `content` | `string` | Yes | The instruction text with placeholders. | :::note **`testedWith` is required for Skills.** Every skill must declare which model it was tested with. This is a key difference from Prompts, which do not require `testedWith`. The field records accountability -- if a skill produces unreliable results, the tested model is the first variable to check. ::: ### `requires` Object | Field | Type | Description | |-------|------|-------------| | `tools` | `string[]` | Tool references this skill uses. Uses the full ID format (`namespace/tool/name`) for cross-schema references, or plain names for same-schema tools. | | `resources` | `string[]` | Resource names this skill reads. Must match names in `main.resources`. | | `external` | `string[]` | External capabilities not provided by the schema (e.g. `playwright`, `puppeteer`). For documentation purposes. | :::note **`requires.external` is Skills-only.** Prompts do not have an `external` field. Use it to document dependencies that exist outside the FlowMCP ecosystem, so consumers know what additional setup is needed. ::: ### `input` Array Each input parameter: | Field | Type | Description | |-------|------|-------------| | `key` | `string` | Parameter name (camelCase). Referenced in content as `{{input:key}}`. | | `type` | `string` | One of `string`, `number`, `boolean`, or `enum`. | | `description` | `string` | What this input parameter is for. | | `required` | `boolean` | Whether the user must provide this value. | ## Placeholder Syntax The `content` field supports four placeholder types, identical to the Prompts placeholder system: | Placeholder | Syntax | Resolves To | Example | |-------------|--------|-------------|---------| | Tool reference | `{{tool:name}}` | Tool name from `requires.tools` | `{{tool:computeRSI}}` | | Resource reference | `{{resource:name}}` | Resource name from `requires.resources` | `{{resource:verifiedContracts}}` | | Skill reference | `{{skill:name}}` | Another skill in the same schema | `{{skill:quick-check}}` | | Input reference | `{{input:key}}` | User-provided input value | `{{input:tokenSymbol}}` | ### Placeholder Rules 1. `{{tool:x}}` -- `x` should be listed in `requires.tools` 2. `{{resource:x}}` -- `x` should be listed in `requires.resources` 3. `{{skill:x}}` -- must reference another skill in the same schema (no circular references) 4. `{{input:x}}` -- should match an `input[].key` Unresolved placeholders produce validation warnings (not errors), except for `{{skill:x}}` which must resolve. ## Constraints | Constraint | Value | Rationale | |------------|-------|-----------| | Max skills per schema | 4 | Keep schemas focused | | `testedWith` | Required | Accountability for non-deterministic workflows | | `requires.external` | Skills-only | Prompts do not support this field | | Skill name pattern | `^[a-z][a-z0-9-]*$` | Lowercase with hyphens | | Skill file extension | `.mjs` | ES module format | | Version | `flowmcp-skill/1.0.0` | Fixed for v3.0.0 | | Content | Non-empty string | Must contain instructions | | No circular references | Via `{{skill:x}}` | Prevents infinite loops | ## Skills vs Prompts Summary | Aspect | Prompt | Skill | |--------|--------|-------| | Purpose | Explain context | Instruct step-by-step | | `testedWith` | Optional | **Required** | | `requires.external` | Not available | Available | | Placeholder system | Same | Same | | Max per schema | 4 | 4 | | File format | `.mjs` | `.mjs` | | MCP primitive | Prompts | Prompts | --- # Tools /docs/schemas/tools Tools wrap REST API endpoints. Each tool maps to one HTTP request. Schemas are `.mjs` files with two named exports: a static `main` block and an optional `handlers` factory function. :::note This page focuses on practical tool creation. See Schema Format for the full specification and Parameters for parameter details. ::: ## Schema Structure A schema file has two parts: the declarative `main` export that describes what tools do, and the optional `handlers` export that transforms requests and responses. ## The `main` Export The `main` export is a static, JSON-serializable object. No functions, no dynamic values, no imports. ### Required Fields | Field | Type | Description | |-------|------|-------------| | `namespace` | `string` | Provider identifier, lowercase letters only (`/^[a-z]+$/`). | | `name` | `string` | Schema name in PascalCase (e.g. `SmartContractExplorer`). | | `description` | `string` | What this schema does, 1-2 sentences. | | `version` | `string` | Must match `3.\d+.\d+` (semver, major must be `3`). | | `root` | `string` | Base URL for all tools. Must start with `https://` (no trailing slash). | | `tools` | `object` | Tool definitions. Keys are camelCase tool names. Maximum 8 tools. | ### Optional Fields | Field | Type | Description | |-------|------|-------------| | `docs` | `string[]` | Documentation URLs for the API provider. | | `tags` | `string[]` | Categorization tags for tool discovery. | | `requiredServerParams` | `string[]` | Environment variable names needed at runtime (e.g. API keys). | | `requiredLibraries` | `string[]` | npm packages needed by handlers. | | `headers` | `object` | Default HTTP headers applied to all tools. | | `sharedLists` | `object[]` | Shared list references for dynamic enum values. See Shared Lists. | ```javascript export const main = { namespace: 'etherscan', name: 'SmartContractExplorer', description: 'Explore verified smart contracts on EVM chains via Etherscan APIs', version: '3.0.0', root: 'https://api.etherscan.io', docs: [ 'https://docs.etherscan.io/' ], tags: [ 'ethereum', 'blockchain' ], requiredServerParams: [ 'ETHERSCAN_API_KEY' ], tools: { // tool definitions here } } ``` ## Tool Definition Each key in `tools` is the tool name in camelCase. The tool name becomes part of the fully qualified MCP tool name. ### Tool Fields | Field | Type | Required | Description | |-------|------|----------|-------------| | `method` | `string` | Yes | HTTP method: `GET`, `POST`, `PUT`, `DELETE`. | | `path` | `string` | Yes | URL path appended to `root`. May contain `{{key}}` placeholders. | | `description` | `string` | Yes | What this tool does. Appears in the MCP tool description. | | `parameters` | `array` | Yes | Input parameter definitions. Can be empty `[]`. | ### Path Templates The path supports `{{key}}` placeholders that are replaced by `insert` parameters at call-time: ```javascript // Static path path: '/api' // Single placeholder path: '/api/v1/{{address}}/transactions' // Multiple placeholders path: '/api/v1/{{chainId}}/address/{{address}}/balances' ``` Every `{{key}}` placeholder must have a corresponding parameter with `location: 'insert'`. ## Parameters Each parameter has two blocks: `position` (where the value goes) and `z` (how it is validated). ### Parameter Types | Type | Description | Example | |------|-------------|---------| | `string()` | Any string value | `'string()'` | | `number()` | Numeric value | `'number()'` | | `boolean()` | True or false | `'boolean()'` | | `enum(A,B,C)` | One of the listed values | `'enum(mainnet,testnet)'` | | `array()` | Array of values | `'array()'` | ### Value Sources | Pattern | Description | Visible to User | |---------|-------------|-----------------| | `{{USER_PARAM}}` | User provides the value at call-time | Yes | | `{{SERVER_PARAM:KEY}}` | Injected from environment variable | No | | Fixed string | Sent automatically with every request | No | ### Validation Options | Option | Description | Example | |--------|-------------|---------| | `min(n)` | Minimum value or length | `'min(1)'` | | `max(n)` | Maximum value or length | `'max(100)'` | | `optional()` | Parameter is not required | `'optional()'` | | `default(value)` | Default when omitted | `'default(100)'` | ```javascript // User-provided address with length validation { position: { key: 'address', value: '{{USER_PARAM}}', location: 'query' }, z: { primitive: 'string()', options: [ 'min(42)', 'max(42)' ] } } // Fixed parameter (invisible to user) { position: { key: 'module', value: 'contract', location: 'query' }, z: { primitive: 'string()', options: [] } } // API key injected from environment { position: { key: 'apikey', value: '{{SERVER_PARAM:ETHERSCAN_API_KEY}}', location: 'query' }, z: { primitive: 'string()', options: [] } } ``` :::tip Fixed parameters are common for APIs like Etherscan that use query parameters for routing (`module=contract`, `action=getabi`). They let multiple tools share the same `root` + `path`. ::: ## Handlers The optional `handlers` export is a factory function that receives injected dependencies and returns handler objects per tool. ```javascript export const handlers = ( { sharedLists, libraries } ) => ({ toolName: { preRequest: async ( { struct, payload } ) => { // modify the request before it is sent return { struct, payload } }, postRequest: async ( { response, struct, payload } ) => { // transform the response after receiving it return { response } } } }) ``` ### Injected Dependencies | Parameter | Type | Description | |-----------|------|-------------| | `sharedLists` | `object` | Resolved shared list data, keyed by list name. Read-only (deep-frozen). | | `libraries` | `object` | Loaded npm packages from `requiredLibraries`, keyed by package name. | ### Handler Types | Handler | When | Input | Must Return | |---------|------|-------|-------------| | `preRequest` | Before the API call | `{ struct, payload }` | `{ struct, payload }` | | `postRequest` | After the API call | `{ response, struct, payload }` | `{ response }` | ### Handler Rules 1. **Handlers are optional.** Tools without handlers make direct API calls. 2. **Zero import statements.** All dependencies come through the factory function. 3. **No restricted globals.** `fetch`, `fs`, `process`, `eval` are forbidden. 4. **Return shape must match.** `preRequest` returns `{ struct, payload }`. `postRequest` returns `{ response }`. :::caution Schema files must have zero `import` statements. All external dependencies are declared in `requiredLibraries` and injected at runtime. ::: ## Complete Example A full Etherscan schema with two tools, API key injection, and a `postRequest` handler: ```javascript export const main = { namespace: 'etherscan', name: 'SmartContractExplorer', description: 'Ethereum blockchain explorer API', version: '3.0.0', docs: [ 'https://docs.etherscan.io/' ], tags: [ 'ethereum', 'blockchain' ], root: 'https://api.etherscan.io', requiredServerParams: [ 'ETHERSCAN_API_KEY' ], headers: { 'Accept': 'application/json' }, tools: { getContractAbi: { method: 'GET', path: '/api', description: 'Get the ABI of a verified smart contract', parameters: [ { position: { key: 'module', value: 'contract', location: 'query' }, z: { primitive: 'string()', options: [] } }, { position: { key: 'action', value: 'getabi', location: 'query' }, z: { primitive: 'string()', options: [] } }, { position: { key: 'address', value: '{{USER_PARAM}}', location: 'query' }, z: { primitive: 'string()', options: [ 'min(42)', 'max(42)' ] } }, { position: { key: 'apikey', value: '{{SERVER_PARAM:ETHERSCAN_API_KEY}}', location: 'query' }, z: { primitive: 'string()', options: [] } } ] }, getSourceCode: { method: 'GET', path: '/api', description: 'Get the Solidity source code of a verified smart contract', parameters: [ { position: { key: 'module', value: 'contract', location: 'query' }, z: { primitive: 'string()', options: [] } }, { position: { key: 'action', value: 'getsourcecode', location: 'query' }, z: { primitive: 'string()', options: [] } }, { position: { key: 'address', value: '{{USER_PARAM}}', location: 'query' }, z: { primitive: 'string()', options: [ 'min(42)', 'max(42)' ] } }, { position: { key: 'apikey', value: '{{SERVER_PARAM:ETHERSCAN_API_KEY}}', location: 'query' }, z: { primitive: 'string()', options: [] } } ] } } } export const handlers = ( { sharedLists } ) => ({ getContractAbi: { postRequest: async ( { response } ) => { return { response: JSON.parse( response['result'] ) } } }, getSourceCode: { postRequest: async ( { response } ) => { const { result } = response const [ first ] = result const { SourceCode, ABI, ContractName, CompilerVersion, OptimizationUsed } = first return { response: { contractName: ContractName, compilerVersion: CompilerVersion, optimizationUsed: OptimizationUsed === '1', sourceCode: SourceCode, abi: ABI } } } } }) ``` :::tip This example demonstrates: fixed parameters (`module`, `action`), user parameters (`address`), server parameter injection (`apikey`), and `postRequest` handlers that flatten nested API responses into clean output. ::: ## Constraints | Constraint | Value | Rationale | |------------|-------|-----------| | Max tools per schema | 8 | Keeps schemas focused. Split large APIs into multiple schema files. | | Version major | `3` | Must match `3.\d+.\d+`. | | Namespace pattern | `^[a-z]+$` | Letters only. No numbers, hyphens, or underscores. | | Root URL protocol | `https://` | HTTP is not allowed. | | Root URL trailing slash | Forbidden | `root` must not end with `/`. | | Schema file imports | Zero | All dependencies are injected via the `handlers` factory. | --- # "Agents" /docs/specification/agents Agents are purpose-driven tool compositions that bundle tools from multiple providers for a specific task. They replace the simpler "Groups" concept from v2.0.0 with full manifest definitions including model binding, system prompts, and testable behavior. :::note This page documents the v3.0.0 Agent system. For the full specification, see 06-agents.md (https://github.com/FlowMCP/flowmcp-spec/blob/main/spec/v3.0.0/06-agents.md). ::: ## Agent vs Provider | Aspect | Provider | Agent | |--------|----------|-------| | **Scope** | Single API source | Multiple providers combined | | **Prompts** | Model-neutral | Model-specific with `testedWith` | | **Model** | None (any model can use) | Bound to specific LLM | | **Purpose** | Expose data | Accomplish tasks | | **Tests** | Per-tool deterministic | Tool selection + content assertions | ## Manifest Format Each agent is defined by a `manifest.json` file in the catalog's `agents/` directory. ```json { "name": "crypto-research", "version": "flowmcp/3.0.0", "description": "Cross-provider crypto analysis agent", "model": "anthropic/claude-sonnet-4-5-20250929", "systemPrompt": "You are a crypto research analyst...", "tools": [ "coingecko-com/tool/simplePrice", "etherscan/tool/getContractAbi", "defillama/tool/getProtocolTvl" ], "tests": [ { "_description": "Basic token lookup", "input": "What is the current price of Ethereum?", "expectedTools": ["coingecko-com/tool/simplePrice"], "expectedContent": ["price", "USD"] } ] } ``` ## Required Fields | Field | Type | Description | |-------|------|-------------| | `name` | string | Agent name (kebab-case) | | `description` | string | Human-readable purpose | | `version` | string | Must be `"flowmcp/3.0.0"` | | `model` | string | Target LLM (OpenRouter format with `/`) | | `systemPrompt` | string | Agent persona and behavioral instructions | | `tools` | string[] | Tool IDs in `namespace/type/name` format | | `tests` | array | Minimum 3 test cases | ## Optional Fields | Field | Type | Default | Description | |-------|------|---------|-------------| | `maxRounds` | number | 10 | Maximum LLM interaction rounds | | `maxTokens` | number | 4096 | Maximum tokens per response | | `prompts` | string[] | [] | Relative paths to prompt files | | `sharedLists` | string[] | [] | Required shared list names | | `inputSchema` | object | — | JSON Schema for agent input | ## Agent Tests Each agent must have at least 3 tests. Tests validate tool selection (deterministic) and optionally content assertions. ```json { "_description": "Cross-provider DeFi analysis", "input": "Compare TVL of Aave on Ethereum vs Arbitrum", "expectedTools": ["defillama/tool/getProtocolTvl"], "expectedContent": ["TVL", "Ethereum", "Arbitrum"] } ``` - **`expectedTools`** — Deterministic: the agent must call exactly these tools - **`expectedContent`** — Partial: the response should contain these strings ## Validation Rules | Code | Rule | |------|------| | AGT001 | `manifest.json` must exist and be valid JSON | | AGT002 | `name` must be kebab-case | | AGT003 | `version` must be `"flowmcp/3.0.0"` | | AGT004 | `model` must use OpenRouter format (contains `/`) | | AGT005 | `tools[]` must not be empty | | AGT006 | Each tool ID must be valid (`namespace/type/name`) | | AGT007 | `tests[]` must have minimum 3 entries | | AGT008 | Each test must have `_description`, `input`, `expectedTools` | ## CLI Commands ```bash # Import an agent from a catalog flowmcp import-agent crypto-research # The agent's tools are activated locally flowmcp list ``` --- # "Catalog" /docs/specification/catalog A Catalog is the top-level organizational unit in FlowMCP v3. It is a named directory containing a `registry.json` manifest that describes all shared lists, provider schemas, and agent definitions. :::note For the full specification, see 15-catalog.md (https://github.com/FlowMCP/flowmcp-spec/blob/main/spec/v3.0.0/15-catalog.md). ::: ## Catalog Structure ``` flowmcp-community/ ├── registry.json # Catalog manifest ├── _lists/ # Shared lists (root level) ├── _shared/ # Shared helpers (root level) ├── providers/ # Provider schemas │ ├── coingecko-com/ │ │ ├── simplePrice.mjs │ │ └── prompts/ │ └── etherscan/ │ └── contracts.mjs └── agents/ # Agent definitions ├── crypto-research/ │ ├── manifest.json │ └── prompts/ └── defi-monitor/ └── manifest.json ``` ## Registry Manifest The `registry.json` file declares everything the catalog contains: ```json { "name": "flowmcp-community", "version": "3.0.0", "description": "Official FlowMCP community catalog", "schemaSpec": "3.0.0", "shared": [ { "file": "_lists/evm-chains.mjs", "name": "evmChains" } ], "schemas": [ { "namespace": "coingecko-com", "file": "providers/coingecko-com/simplePrice.mjs", "name": "CoinGecko Simple Price", "requiredServerParams": [], "hasHandlers": false, "sharedLists": [] } ], "agents": [ { "name": "crypto-research", "description": "Cross-provider crypto analysis", "manifest": "agents/crypto-research/manifest.json" } ] } ``` ## Multiple Catalogs Multiple catalogs can coexist side by side. Each is fully self-contained with no cross-catalog dependencies. ``` schemas/v3.0.0/ ├── flowmcp-community/ # Official catalog ├── my-company-tools/ # Company-internal └── experimental/ # Personal experiments ``` ## Import Flow ```bash # Phase 1: Download catalog flowmcp import-registry https://example.com/registry.json # Phase 2: Activate an agent flowmcp import-agent crypto-research ``` ## Validation ```bash flowmcp validate-catalog ``` | Code | Rule | |------|------| | CAT001 | `registry.json` must exist | | CAT002 | `name` must match directory name | | CAT003 | All `shared[].file` paths must resolve | | CAT004 | All `schemas[].file` paths must resolve | | CAT005 | All `agents[].manifest` paths must resolve | | CAT006 | Orphaned files reported as warnings | | CAT007 | `schemaSpec` must be valid version | --- # "Groups & Skills" /docs/specification/groups-prompts Groups let you create named collections of specific tools, resources, and skills from across multiple schemas. Skills attach reusable AI agent workflows to groups, guiding AI agents through multi-step tasks. :::note This page combines the Groups and Group Skills sections from the formal specification (https://github.com/FlowMCP/flowmcp-spec). In v3.0.0, "Group Prompts" have been renamed to "Group Skills" to align with MCP terminology. ::: ## Groups A typical FlowMCP installation has hundreds of schemas with thousands of tools. Most projects only need a handful of specific tools. Groups solve this by letting you: 1. **Select specific tools and resources** from any schema 2. **Name the collection** for reuse 3. **Verify integrity** with cryptographic hashes 4. **Share collections** across projects and teams ### Group Definition Groups are defined in `.flowmcp/groups.json`: ```json { "specVersion": "3.0.0", "groups": { "my-crypto-monitor": { "description": "Crypto price and TVL monitoring tools", "tools": [ "etherscan/contracts.mjs::tool::getContractAbi", "coingecko/coins.mjs::tool::getSimplePrice", "coingecko/coins.mjs::tool::getCoinMarkets", "defillama/protocols.mjs::tool::getTvlProtocol", "etherscan/contracts.mjs::resource::verifiedContracts" ], "hash": "sha256:a1b2c3d4e5f6...", "includeSchemaSkills": true } } } ``` ### Type Discriminator Syntax In v3.0.0, tool references use type discriminators to distinguish between tools, resources, and skills: | Discriminator | Format | Example | |---------------|--------|---------| | `::tool::` | `namespace/file.mjs::tool::name` | `etherscan/contracts.mjs::tool::getContractAbi` | | `::resource::` | `namespace/file.mjs::resource::name` | `etherscan/contracts.mjs::resource::verifiedContracts` | | `::skill::` | `namespace/file.mjs::skill::name` | `etherscan/contracts.mjs::skill::contract-audit` | For backward compatibility, the v2 format `namespace/file.mjs::routeName` (without type discriminator) is still accepted and is treated as `::tool::`. ### `includeSchemaSkills` When `includeSchemaSkills` is set to `true`, the group automatically includes all skills from schemas whose tools are already in the group. ### Hash Verification Integrity hashes ensure group definitions haven't changed unexpectedly. **Per-tool hash** — calculated from the `main` block only (no handler code): ``` toolHash = SHA-256( JSON.stringify( { namespace, version, tool: { name, method, path, parameters, output }, sharedListRefs: [ { ref, version } ] } ) ) ``` **Per-group hash** — calculated from sorted tool references and their individual hashes: ``` groupHash = SHA-256( JSON.stringify( tools.sort().map( ( toolRef ) => ({ ref: toolRef, hash: getToolHash( toolRef ) }) ) ) ) ``` ### Verification CLI ```bash flowmcp group verify my-crypto-monitor ``` ```bash # Success Group "my-crypto-monitor": 4 tools, all hashes valid # Hash Mismatch Group "my-crypto-monitor": HASH MISMATCH - etherscan/contracts.mjs::tool::getContractAbi: expected sha256:abc... got sha256:def... ``` ### Group Operations | Operation | Command | Description | |-----------|---------|-------------| | Create | `flowmcp group create ` | Create empty group | | Add tool | `flowmcp group add ` | Add tool and recalculate hash | | Remove tool | `flowmcp group remove ` | Remove tool and recalculate hash | | Verify | `flowmcp group verify ` | Check all hashes | | List | `flowmcp group list` | Show all groups | | Export | `flowmcp group export ` | Export as shareable JSON | | Import | `flowmcp group import ` | Import from JSON (verifies hashes) | ### Group Constraints | Constraint | Value | |------------|-------| | Name pattern | `^[a-z][a-z0-9-]*$` (lowercase, hyphens allowed) | | Max tools per group | 50 | | All tools must be resolvable | Schema + tool must exist | | No duplicate tool references | Within a group | --- ## Group Skills Skills bridge the deterministic tool layer with non-deterministic AI orchestration. Groups define **which** tools are available; skills define **how** to use them together. ### Separation of Concerns | Layer | Nature | Responsibility | |-------|--------|----------------| | Schema | Deterministic | Defines individual tool behavior | | Group | Deterministic | Defines which tools are available | | Skill | Non-deterministic | Defines how tools compose into workflows | ### Skill File Format Group-level skills are stored as `.md` files in `.flowmcp/skills/`: ``` .flowmcp/ ├── groups.json ├── skills/ │ ├── token-analysis.md │ └── portfolio-snapshot.md └── tools/ ``` :::note Group-level skills (`.md` files in `.flowmcp/skills/`) are different from schema-level skills (`.mjs` files alongside the schema). Group-level skills are project-specific workflows. Schema-level skills are part of the schema's MCP Prompts. See Skills for schema-level skill documentation. ::: ### Required Sections | Section | Heading | Required | Description | |---------|---------|----------|-------------| | Title | `# ` | Yes | First line of the file | | Description | `## Description` | No | 1-3 sentences about the skill | | Input | `## Input` | No | Parameters the user provides | | Workflow | `## Workflow` | Yes | Step-by-step instructions referencing tools | | Output | `## Output` | No | Final artifact description | ### Example Skill File ```markdown # Standard Token Analysis ## Description Generate a comprehensive technical analysis report for any financial instrument. ## Input - `tokenName` (string, required): Name or ticker symbol of the instrument ## Workflow ### Step 1: Symbol Resolution Search for `{tokenName}` using `searchSymbol`. ### Step 2: Fetch Price Data Call `getOhlcv` with interval: 1d, period1: 200 days ago. ### Step 3: Compute Indicators Compute `getRelativeStrengthIndex`, `getSimpleMovingAverage`, `getMovingAverageConvergenceDivergence`. ### Step 4: Generate Charts Call `generateCandlestickChart` and `generateLineChart`. ### Step 5: Report Produce a Markdown document with indicator summary and embedded charts. ## Output - Markdown document with embedded base64 chart images - Indicator summary with BUY/SELL/HOLD signals ``` ### Skill CLI Commands | Command | Description | |---------|-------------| | `flowmcp skill list` | List all skills across all groups | | `flowmcp skill search <query>` | Search skills by title or description | | `flowmcp skill show <group>/<name>` | Display full skill content | | `flowmcp skill add <group> <name> --file <path>` | Add skill to a group | | `flowmcp skill remove <group> <name>` | Remove skill from group | ### Skill Validation Rules | Code | Rule | |------|------| | PRM001 | Skill name must match `^[a-z][a-z0-9-]*$` | | PRM002 | File must exist at declared path | | PRM003 | File must have `# Title` (first line) | | PRM004 | File must have `## Workflow` section | | PRM005 | Tool references must resolve in group (warning) | | PRM006 | Group must have at least one tool | | PRM007 | No duplicate skill names within a group | | PRM008 | Filename must match skill name | --- # "ID Schema" /docs/specification/id-schema FlowMCP v3.0.0 introduces a unified ID format for referencing any element in the catalog: tools, resources, prompts, and lists. :::note For the full specification, see 16-id-schema.md (https://github.com/FlowMCP/flowmcp-spec/blob/main/spec/v3.0.0/16-id-schema.md). ::: ## Format ``` namespace/resourceType/name ``` | Component | Pattern | Examples | |-----------|---------|----------| | `namespace` | `[a-z][a-z0-9-]*` | `coingecko-com`, `etherscan`, `defillama` | | `resourceType` | `tool`, `resource`, `prompt`, `list` | — | | `name` | `[a-zA-Z][a-zA-Z0-9]*` | `simplePrice`, `getContractAbi` | ## Examples | Full Form | Short Form | Context | |-----------|------------|---------| | `coingecko-com/tool/simplePrice` | `coingecko-com/simplePrice` | Tool reference | | `etherscan/resource/contracts` | `etherscan/contracts` | Resource reference | | `crypto-research/prompt/token-deep-dive` | — | Prompt reference | | `shared/list/evmChains` | `shared/evmChains` | Shared list | ## Short Form The short form `namespace/name` is allowed when the resource type is unambiguous: - In `manifest.tools[]` — always means `tool` - In `manifest.sharedLists[]` — always means `list` - In `prompt.dependsOn[]` for Agent-Prompts — always means `tool` ## Usage in Placeholders In prompt content, `[[...]]` with `/` is resolved as an ID reference: ``` Use [[coingecko-com/tool/simplePrice]] to get the price of [[tokenId]]. ``` - `[[coingecko-com/tool/simplePrice]]` — Reference (resolved via ID schema) - `[[tokenId]]` — Parameter (user input, no `/`) ## Validation Rules | Code | Rule | |------|------| | ID001 | ID must contain at least one `/` separator | | ID002 | Namespace must match `[a-z][a-z0-9-]*` | | ID003 | Resource type (if present) must be `tool`, `resource`, `prompt`, or `list` | | ID004 | Name must match `[a-zA-Z][a-zA-Z0-9]*` | | ID005 | Short form allowed only in unambiguous contexts | | ID006 | Reserved namespace `shared` for shared lists only | --- # "Migration Guide" /docs/specification/migration This guide covers migrating schemas between FlowMCP versions. Both migration paths are documented below. :::note The full migration specification is maintained at github.com/FlowMCP/flowmcp-spec (https://github.com/FlowMCP/flowmcp-spec). This page provides a practical walkthrough. ::: ## v2 to v3 Migration The v3.0.0 release renames `routes` to `tools` and adds two new MCP primitives: **Resources** (SQLite-based read-only data) and **Skills** (reusable AI agent instructions). ### What Changed | v2 | v3 | Description | |----|-----|-------------| | `main.routes` | `main.tools` | Primary key rename — `routes` accepted as deprecated alias | | `version: '2.0.0'` | `version: '3.0.0'` | Version field update | | `namespace/file.mjs::routeName` | `namespace/file.mjs::tool::routeName` | Type discriminator in group references | | `prompts` (in groups) | `skills` (in groups) | Group prompts renamed to group skills | | — | `main.resources` | New: SQLite-based read-only data | | — | `main.skills` | New: AI agent instruction files | | — | `includeSchemaSkills` | New: Auto-include schema skills in groups | ### Automated Migration ```bash # Migrate a single schema flowmcp migrate ./schemas/coingecko/Ping.mjs # Migrate all schemas in a directory flowmcp migrate --all ./schemas/ # Preview changes without writing (dry run) flowmcp migrate --dry-run ./schemas/coingecko/Ping.mjs ``` :::tip Use `--dry-run` first to preview what changes will be made without modifying any files. ::: ### Manual Migration Steps 1. **Rename routes to tools** — The primary change: rename the `routes` key to `tools` and update `version: '3.0.0'`. ```javascript // Before (v2) export const main = { namespace: 'coingecko', version: '2.0.0', routes: { ping: { method: 'GET', path: '/ping', description: 'Check API status', parameters: [] } } } // After (v3) export const main = { namespace: 'coingecko', version: '3.0.0', tools: { ping: { method: 'GET', path: '/ping', description: 'Check API status', parameters: [] } } } ``` 2. **Update version field** — Change `version: '2.0.0'` to `version: '3.0.0'`. 3. **Update group references** — If your project uses groups in `.flowmcp/groups.json`, add type discriminators: ```json // Before "tools": [ "etherscan/contracts.mjs::getContractAbi" ] // After "tools": [ "etherscan/contracts.mjs::tool::getContractAbi" ] ``` 4. **Add resources (optional)** — If your schema benefits from local data lookups, see Resources. 5. **Add skills (optional)** — If your schema benefits from AI agent workflows, see Skills. 6. **Run validation** — `flowmcp validate <schema-path>` ### Deprecation Timeline | Version | `routes` Behavior | |---------|-------------------| | **v3.0.0** | `routes` accepted as silent alias for `tools` | | **v3.1.0** | `routes` accepted with loud deprecation warning | | **v3.2.0** | `routes` rejected with error | --- ## v1 to v2 Migration ### Schema Categories | Category | % of Schemas | Migration Effort | Description | |----------|-------------|-----------------|-------------| | **Pure declarative** | ~60% | Automatic | No handlers, no imports. | | **With handlers** | ~30% | Semi-automatic | Has handlers but no imports. | | **With imports** | ~10% | Manual review | Imports shared data that must become shared list references. | ### Migration Steps 1. **Wrap existing fields in main block** — The biggest structural change: ```javascript // Before (v1.2.0) export const schema = { namespace: 'etherscan', flowMCP: '1.2.0', root: 'https://api.etherscan.io/v2/api', routes: { /* ... */ }, handlers: { /* ... */ } } // After (v2.0.0) export const main = { namespace: 'etherscan', version: '2.0.0', root: 'https://api.etherscan.io/v2/api', requiredLibraries: [], routes: { /* ... */ } } export const handlers = ( { sharedLists, libraries } ) => ({ /* ... */ }) ``` 2. **Update version field** — `flowMCP: '1.2.0'` becomes `version: '2.0.0'` inside `main`. 3. **Convert imports to shared list references**: ```javascript // Before (v1.2.0) — has import import { evmChains } from '../_shared/evm-chains.mjs' // ...handlers use evmChains directly // After (v2.0.0) — no import, uses injection export const main = { sharedLists: [ { ref: 'evmChains', version: '1.0.0' } ], // ... } export const handlers = ( { sharedLists } ) => ({ toolName: { preRequest: async ( { struct, payload } ) => { const chain = sharedLists.evmChains.find( c => c.alias === payload.chainName ) return { struct, payload } } } }) ``` 4. **Add output schemas (optional)** — New in v2.0.0. See Output Schema. 5. **Run validation** — `flowmcp validate <schema-path>` ### Common Migration Issues | Issue | Cause | Fix | |-------|-------|-----| | `SEC001: Forbidden pattern "import"` | Import statement still present | Convert to `sharedLists` reference | | `VAL003: "flowMCP" is not a valid field` | Old version field | Change to `version` inside `main` | | `DEP001: main.routes is deprecated` | v2 schema on v3 runtime | Rename `routes` to `tools` | ### Migration Checklist **v1 to v2:** - [ ] Fields wrapped in `main` block - [ ] `flowMCP: '1.2.0'` changed to `version: '2.0.0'` inside `main` - [ ] `handlers` at top level (sibling of `main`) - [ ] All `import` statements removed - [ ] Imported data converted to `sharedLists` references - [ ] `requiredLibraries` declared (can be empty `[]`) - [ ] Full validation passes (`flowmcp validate`) **v2 to v3:** - [ ] `routes` renamed to `tools` - [ ] `version` changed from `2.x.x` to `3.0.0` - [ ] Group references updated with type discriminators (optional in v3.0.0) - [ ] Full validation passes (`flowmcp validate`) --- # "Output Schema" /docs/specification/output-schema Output schemas make tool responses predictable. AI clients can know in advance what shape the data will have, enabling structured reasoning without parsing guesswork. :::note This page covers output schemas from the formal specification (https://github.com/FlowMCP/flowmcp-spec). See Route Tests for how output schemas are generated from captured responses. ::: ## Purpose Without output schemas, an AI client calling a FlowMCP tool receives an opaque blob of JSON. Output schemas solve this by declaring the expected response shape at the route level: - **AI clients** can pre-allocate structured reasoning about response fields - **Schema validators** can verify handler output matches the declaration - **Documentation generators** can produce accurate response tables automatically ## Route-Level Output Definition Each route can optionally define an `output` field: ```javascript routes: { getTokenPrice: { method: 'GET', path: '/simple/price', description: 'Get current token price', parameters: [ /* ... */ ], output: { mimeType: 'application/json', schema: { type: 'object', properties: { id: { type: 'string', description: 'Token identifier' }, symbol: { type: 'string', description: 'Token symbol' }, price: { type: 'number', description: 'Current price in USD' }, marketCap: { type: 'number', description: 'Market capitalization', nullable: true }, volume24h: { type: 'number', description: 'Trading volume (24h)' } } } } } } ``` ## Output Fields | Field | Type | Required | Description | |-------|------|----------|-------------| | `mimeType` | `string` | Yes | Response content type | | `schema` | `object` | Yes | Simplified JSON Schema describing the `data` field | ## Supported MIME Types | MIME Type | Description | Schema `type` | |-----------|-------------|---------------| | `application/json` | JSON response (default) | `object` or `array` | | `image/png` | PNG image, base64-encoded | `string` with `format: 'base64'` | | `text/plain` | Plain text response | `string` | ## Standard Response Envelope Every FlowMCP tool response is wrapped in a standard envelope: ```javascript // Success Response { status: true, messages: [], data: { /* described by output.schema */ } } // Error Response { status: false, messages: [ 'E001 getTokenPrice: API returned 404' ], data: null } ``` | Field | Type | Description | |-------|------|-------------| | `status` | `boolean` | `true` on success, `false` on error | | `messages` | `array` | Empty on success, error descriptions on failure | | `data` | `object` or `null` | Response payload on success, `null` on error | :::tip The `output.schema` describes **only the `data` field** when `status: true`. Schema authors do not declare the envelope — it is implicit and standardized. ::: ## Supported JSON Schema Subset FlowMCP uses a deliberately constrained subset of JSON Schema: ### Supported Keywords | Keyword | Description | Example | |---------|-------------|---------| | `type` | Value type | `'string'`, `'number'`, `'boolean'`, `'object'`, `'array'` | | `properties` | Object properties | `{ name: { type: 'string' } }` | | `items` | Array item schema | `{ type: 'object', properties: {...} }` | | `description` | Human-readable description | `'Current price in USD'` | | `nullable` | Can be `null` | `true` | | `enum` | Allowed values | `['active', 'inactive']` | | `format` | Special format hint | `'base64'`, `'date-time'`, `'uri'` | ### Intentionally Excluded Keywords - `$ref` — no schema references; output schemas are self-contained - `oneOf`, `anyOf`, `allOf` — no union types - `required` — all declared properties are informational - `additionalProperties` — APIs may return extra fields - `pattern`, `minimum`, `maximum` — no regex or range validation on output ## Response Examples ### Object Response ```javascript output: { mimeType: 'application/json', schema: { type: 'object', properties: { id: { type: 'string', description: 'Token identifier' }, price: { type: 'number', description: 'Current price in USD' }, marketCap: { type: 'number', description: 'Market cap', nullable: true } } } } ``` ### Array Response ```javascript output: { mimeType: 'application/json', schema: { type: 'array', items: { type: 'object', properties: { name: { type: 'string', description: 'Protocol name' }, tvl: { type: 'number', description: 'Total value locked in USD' } } } } } ``` ### Image Response ```javascript output: { mimeType: 'image/png', schema: { type: 'string', format: 'base64', description: 'Chart image as base64-encoded PNG' } } ``` ## Nullable Fields Fields that may be `null` in a successful response must declare `nullable: true`: ```javascript properties: { marketCap: { type: 'number', description: 'Market cap', nullable: true }, website: { type: 'string', description: 'Project URL', nullable: true } } ``` ## Non-Blocking Validation Output schema validation is **non-blocking**. A mismatch produces a validation **warning**, not an error. The response is still delivered. ```mermaid flowchart TD A[Handler returns data] --> B{Schema declared?} B -->|No| C[Pass through] B -->|Yes| D{Data matches schema?} D -->|Yes| E[Return data] D -->|No| F[Log warning] F --> G[Return data anyway] ``` --- # "Overview" /docs/specification/overview FlowMCP is a deterministic normalization layer that converts heterogeneous web data sources into uniform, AI-consumable tools. The v3.0.0 specification defines how schemas, shared lists, groups, resources, skills, and security constraints work together to produce reliable, verifiable tool catalogs. :::note This documentation adapts the formal specification for practical use. The full specification is maintained at github.com/FlowMCP/flowmcp-spec (https://github.com/FlowMCP/flowmcp-spec). ::: ## Problem Web data sources are organized by **provider** — Etherscan exposes smart contract endpoints, CoinGecko exposes market data, DeFi Llama exposes TVL metrics. Each source has its own interface style, authentication scheme, URL structure, response format, and error handling. AI agents need tools organized by **application domain** — token prices, contract ABIs, TVL data, wallet balances. An agent answering a question about a token's market cap should not need to know whether the answer comes from CoinGecko, CoinCap, or DeFi Llama. Manual integration per source is unsustainable at scale. With 187+ schemas across dozens of providers, the combinatorial complexity makes hand-written integrations fragile and expensive to maintain. ## Solution FlowMCP introduces a **schema-driven normalization layer** between web data sources and AI clients. Each schema is a `.mjs` file that declaratively defines: - **Input parameters** with Zod-based validation (type, constraints, enum values) - **URL construction rules** (path templates, query parameters, body payloads) - **Response transformation** (optional handlers for pre/post processing) - **Security constraints** (no imports, no filesystem access, no eval) The runtime reads these schemas and exposes them as MCP tools. The AI client sees a flat catalog of tools with typed inputs and predictable outputs — the underlying source complexity is completely abstracted away. ```mermaid flowchart LR A[Web Data Sources] --> B[FlowMCP Schemas] B --> C[Core Runtime] C --> D[MCP Server] D --> E[AI Client] F[Shared Lists] --> C G[Security Scan] --> B H[SQLite DBs] --> C I[Skill Files] --> C ``` ## Three MCP Primitives FlowMCP v3.0.0 supports all three MCP primitives in a single schema: | Primitive | Schema Key | MCP Concept | Description | |-----------|-----------|-------------|-------------| | **Tools** | `tools` | Tools | HTTP endpoints — deterministic API calls with parameter validation | | **Resources** | `resources` | Resources | SQLite-based read-only data lookups — fast, local, no network | | **Skills** | `skills` | Prompts | Reusable AI agent instructions — compose tools and resources into workflows | Tools are the core primitive and the only one required. Resources and skills are optional additions that enhance what a schema can offer. ## Positioning FlowMCP is the **deterministic anchor** in a system that pairs it with non-deterministic AI. | Layer | Nature | Responsibility | |-------|--------|----------------| | AI Client (Skills) | Non-deterministic | Decides *which* tool to use, *how* to interpret results | | FlowMCP | Deterministic | Guarantees the tool itself behaves identically every time | | Web Data Sources | External | Provides the raw data (uncontrolled) | FlowMCP guarantees that: - The same input parameters always produce the same API call - Parameter validation is enforced before any network request - Response transformations are consistent and reproducible - Security constraints are verified at load-time, not runtime ## Terminology | Term | Definition | |------|-----------| | **Schema** | A `.mjs` file with two named exports: `main` (static) and optionally `handlers` (factory function). Defines tools, resources, and/or skills. | | **Tool** | An API endpoint within a schema. Maps to one web data source endpoint. Each tool has parameters, a method, a path, and optional handlers. | | **Resource** | A SQLite-based read-only data lookup within a schema. Uses `sql.js` for query execution. | | **Skill** | A reusable AI agent instruction file (`.mjs`) that composes tools and resources into multi-step workflows. Maps to MCP Prompts. | | **Namespace** | Provider identifier, lowercase letters only (e.g. `etherscan`, `coingecko`). Groups schemas by data source. | | **Handler** | An async function returned by the `handlers` factory. Performs pre- or post-processing for a tool. Receives dependencies via injection. | | **Modifier** | Handler subtype: `preRequest` transforms input before the API call, `postRequest` transforms output after. | | **Shared List** | A reusable, versioned value list (e.g. EVM chain identifiers) referenced by schemas and injected at load-time. | | **Group** | A named collection of cherry-picked tools, resources, and skills with an integrity hash. Used for project-level activation. | | **Main Export** | `export const main = {...}` — the declarative, JSON-serializable part of a schema. Hashable for integrity verification. | | **Handlers Export** | `export const handlers = ({ sharedLists, libraries }) => ({...})` — factory function receiving injected dependencies. | ## Design Principles ### 1. Deterministic over clever Same input always produces the same API call. No randomness, no caching heuristics, no adaptive behavior inside the schema layer. ### 2. Declare over code Maximize the `main` block, minimize handlers. Every field that can be expressed declaratively must live in `main`. Handlers exist only for transformations that cannot be expressed as static data. ### 3. Inject over import Schemas receive data through dependency injection, never import. A handler that needs EVM chain data receives `sharedLists.evmChains` via the factory function. Libraries are declared in `requiredLibraries` and injected by the runtime from an allowlist. ### 4. Hash over trust Integrity verification through SHA-256 hashes. The `main` block is hashable because it is pure JSON-serializable data. Groups store hashes of their member tools. ### 5. Constrain over permit Security by default, explicit opt-in for capabilities. Schema files have zero import statements. The security scanner rejects schemas with forbidden patterns at load-time. ## Version History | Version | Date | Changes | |---------|------|---------| | 1.0.0 | 2025-06 | Initial schema format. Flat structure with inline parameters. | | 1.2.0 | 2025-11 | Added handlers, Zod-based parameter validation, modifier pattern. | | 2.0.0 | 2026-02 | Two-export format (`main` + `handlers` factory). Dependency injection. Shared lists. Output schemas. Zero-import security. Groups with integrity hashes. Max routes reduced to 8. | | 3.0.0 | 2026-03 | `routes` renamed to `tools`. Resources (SQLite). Skills (MCP Prompts). Type discriminators. `includeSchemaSkills`. Migration CLI. | ## What's New in v3.0.0 The v3.0.0 release extends FlowMCP from a tool-only framework to support all three MCP primitives: - **`routes` renamed to `tools`** — aligns with MCP terminology. `routes` is accepted as a deprecated alias with a defined sunset timeline (v3.0.0: alias, v3.1.0: loud warning, v3.2.0: error) - **Resources** — SQLite-based read-only data access via `sql.js`. Declared in the `resources` key with prepared statements and SQL security enforcement - **Skills** — reusable AI agent instructions stored as `.mjs` files. Declared in the `skills` key. Maps to MCP Prompts with placeholder system (`{{tool:x}}`, `{{resource:x}}`, `{{input:x}}`) - **Type discriminators** — `::tool::name`, `::resource::name`, `::skill::name` for unambiguous references in groups - **`includeSchemaSkills`** — groups can auto-include all skills from member schemas - **`flowmcp migrate`** — automated v2 to v3 migration via CLI - **New validation rules** — RES001-RES023 for resources, SKL001-SKL025 for skills, DEP001-DEP004 for deprecation warnings ## Specification Document Index | Document | Description | |----------|-------------| | Schema Format | File structure, main/handlers split, tool definitions, naming conventions | | Parameters | Position block, Z block validation, shared list interpolation, API key injection | | Shared Lists | List format, versioning, field definitions, filtering, resolution lifecycle | | Output Schema | Response type declarations, field mapping, validation rules | | Security Model | Zero-import policy, library allowlist, static scan, dependency injection | | Resources | SQLite-based read-only data, query definitions, SQL security | | Skills | AI agent instructions, placeholder system, versioning | | Groups & Skills | Cherry-pick groups, integrity hashes, skill workflows | | Tests | Test format, response capture lifecycle, output schema generation | | Preload | Cache configuration, TTL guidelines, runtime behavior | | Validation Rules | Validation rules across categories with severity levels | | Migration Guide | Step-by-step migration guides for v1-to-v2 and v2-to-v3 | --- # "Parameters" /docs/specification/parameters Each parameter in a FlowMCP tool describes **where** a value is placed in the API request (`position`) and **how** it is validated (`z`). Both blocks are required. :::note This page covers parameters from the formal specification (https://github.com/FlowMCP/flowmcp-spec). See Shared Lists for list interpolation details. ::: ## Parameter Structure ```javascript { position: { key: 'address', value: '{{USER_PARAM}}', location: 'query' }, z: { primitive: 'string()', options: [ 'min(42)', 'max(42)' ] } } ``` ## Position Block The `position` block controls where the parameter's value is placed in the HTTP request. | Field | Type | Required | Description | |-------|------|----------|-------------| | `key` | `string` | Yes | Parameter name. For user-facing parameters, this is the input field name exposed to the AI client. | | `value` | `string` | Yes | `{{USER_PARAM}}` for user input, `{{SERVER_PARAM:KEY_NAME}}` for server params, or a fixed string. | | `location` | `string` | Yes | Where the value is placed: `insert`, `query`, or `body`. | ### Location Types | Location | Description | Example | |----------|-------------|---------| | `insert` | Inserted into the URL path at the `{{key}}` placeholder | `/api/v1/{{address}}/txs` becomes `/api/v1/0xABC.../txs` | | `query` | Added as a URL query parameter | `?address=0xABC...&module=contract` | | `body` | Added to the JSON request body | `{ "address": "0xABC..." }` | ### Location Rules 1. **`insert` parameters** require a matching `{{key}}` placeholder in the tool's `path`. 2. **`query` parameters** are appended to the URL in array order. 3. **`body` parameters** are only valid for `POST` and `PUT` tools. A `body` parameter on a `GET` or `DELETE` tool causes a load-time error. 4. **Multiple locations** in the same tool are valid. ### Value Types | Value Pattern | Description | Visible to User | |---------------|-------------|-----------------| | `{{USER_PARAM}}` | Value provided by the user at call-time | Yes | | `{{SERVER_PARAM:KEY_NAME}}` | Value injected from server environment | No | | Any other string | Fixed value, sent automatically | No | ## Z Block (Validation) The `z` block defines validation constraints enforced before the API request is made. The name references Zod, the validation library used by the runtime. | Field | Type | Required | Description | |-------|------|----------|-------------| | `primitive` | `string` | Yes | Base type declaration with optional inline values. | | `options` | `string[]` | Yes | Array of validation constraints. Can be empty `[]`. | ### Primitive Types | Primitive | Description | Zod Equivalent | Example | |-----------|-------------|----------------|---------| | `string()` | Any string value | `z.string()` | `'string()'` | | `number()` | Numeric value | `z.number()` | `'number()'` | | `boolean()` | True or false | `z.boolean()` | `'boolean()'` | | `enum(A,B,C)` | One of the listed values | `z.enum(['A','B','C'])` | `'enum(mainnet,testnet)'` | | `array()` | Array of values | `z.array()` | `'array()'` | | `object()` | Nested object | `z.object()` | `'object()'` | :::caution Enum values are comma-separated with **no spaces** around commas. `enum(GET,POST)` is valid; `enum(GET, POST)` is not. At least one value is required. ::: ### Validation Options Options are applied in array order after the primitive type check: | Option | Description | Applies To | Example | |--------|-------------|------------|---------| | `min(n)` | Minimum value or minimum length | `number`, `string` | `'min(1)'` | | `max(n)` | Maximum value or maximum length | `number`, `string` | `'max(100)'` | | `length(n)` | Exact length or exact item count | `string`, `array` | `'length(42)'` | | `optional()` | Parameter is not required | all | `'optional()'` | | `default(value)` | Default value when omitted (implies `optional()`) | all | `'default(100)'` | **Option rules:** - `min()` and `max()` constrain the numeric value for `number()`, or string length for `string()`. - Multiple options combine with AND logic: `[ 'min(1)', 'max(100)' ]` means value must be >= 1 AND <= 100. - Regular expressions are intentionally excluded — type-level validation with `min()`/`max()`/`length()` covers most use cases. ## Resource Parameters Resource query parameters use a simplified format compared to tool parameters. Since resources are local SQLite queries (not HTTP requests), there is no `location` field in the `position` block: ```javascript resources: { verifiedContracts: { source: 'sqlite', database: 'contracts.db', queries: { byAddress: { description: 'Find contract by address', sql: 'SELECT * FROM contracts WHERE address = ?', parameters: [ { key: 'address', type: 'string', description: 'Contract address', required: true } ] } } } } ``` Resource parameters have a flat structure with `key`, `type`, `description`, and `required` fields. They do not use the `position`/`z` blocks because there is no HTTP request to construct. ## Skill Input Format Skills define their input parameters with a similar flat format: ```javascript input: [ { key: 'address', type: 'string', description: 'Ethereum contract address', required: true }, { key: 'chain', type: 'string', description: 'Chain name', required: false } ] ``` Skill inputs are used as template variables in the skill content via `{{input:key}}` placeholders. ## Shared List Interpolation When a parameter's enum values come from a shared list, use the `{{listName:fieldName}}` syntax inside `enum()`: ```javascript { position: { key: 'chainName', value: '{{USER_PARAM}}', location: 'query' }, z: { primitive: 'enum({{evmChains:etherscanAlias}})', options: [] } } ``` At load-time, the runtime resolves `{{evmChains:etherscanAlias}}` by: 1. Finding the shared list named `evmChains` (declared in `main.sharedLists`) 2. Applying any filter defined in the shared list reference 3. Extracting the `etherscanAlias` field from each entry 4. Replacing the placeholder with comma-separated values The result at runtime is equivalent to: ```javascript primitive: 'enum(ETHEREUM_MAINNET,POLYGON_MAINNET,ARBITRUM_MAINNET,OPTIMISM_MAINNET)' ``` ### Interpolation Rules 1. `{{listName:fieldName}}` is **only allowed inside `enum()`**. Using it in other primitives is an error. 2. The referenced list must be declared in `main.sharedLists`. 3. If the shared list reference has a `filter`, only matching entries are used. 4. The `fieldName` must exist in the list's `meta.fields`. 5. Interpolation happens at **load-time**, not call-time. Shared list updates require a schema reload. 6. **Mixed static and interpolated values** are allowed: `enum(custom,{{evmChains:etherscanAlias}})`. ## Fixed Parameters Parameters with a fixed `value` (not `{{USER_PARAM}}` and not `{{SERVER_PARAM:...}}`) are invisible to the user and sent automatically: ```javascript { position: { key: 'module', value: 'contract', location: 'query' }, z: { primitive: 'string()', options: [] } } ``` Fixed parameters are common for APIs that use query parameters for routing (like Etherscan's `module` and `action` parameters). :::tip Fixed parameters let a single `root` + `path` combination serve multiple tools differentiated by fixed query values. ::: ## API Key Injection API keys are injected via the `{{SERVER_PARAM:KEY_NAME}}` syntax: ```javascript { position: { key: 'apikey', value: '{{SERVER_PARAM:ETHERSCAN_API_KEY}}', location: 'query' }, z: { primitive: 'string()', options: [] } } ``` ### Injection Rules 1. The `KEY_NAME` must be declared in `main.requiredServerParams`. 2. Server parameters are **invisible to the AI client**. 3. The runtime resolves the value from the environment variable. If unset, the tool is hidden. 4. Server parameter values are **never logged**. ## Parameter Visibility Summary | Value Pattern | Visible to AI Client | Appears in Input Schema | Source | |---------------|---------------------|------------------------|--------| | `{{USER_PARAM}}` | Yes | Yes | User provides at call-time | | `{{SERVER_PARAM:KEY}}` | No | No | Environment variable | | Fixed string | No | No | Hardcoded in schema | ## Complete Examples ### Query Parameter with Length Validation ```javascript { position: { key: 'contractAddress', value: '{{USER_PARAM}}', location: 'query' }, z: { primitive: 'string()', options: [ 'min(42)', 'max(42)' ] } } ``` ### Enum with Shared List and Default ```javascript { position: { key: 'chain', value: '{{USER_PARAM}}', location: 'query' }, z: { primitive: 'enum({{evmChains:slug}})', options: [ 'default(ethereum)' ] } } ``` ### Body Parameter with Optional Limit ```javascript [ { position: { key: 'version', value: '2', location: 'body' }, z: { primitive: 'string()', options: [] } }, { position: { key: 'query', value: '{{USER_PARAM}}', location: 'body' }, z: { primitive: 'object()', options: [] } }, { position: { key: 'limit', value: '{{USER_PARAM}}', location: 'body' }, z: { primitive: 'number()', options: [ 'optional()', 'default(100)', 'min(1)', 'max(1000)' ] } } ] ``` --- # "Preload" /docs/specification/preload The optional `preload` field on routes signals that the response is static or slow-changing and that the runtime may cache it locally. This avoids redundant API calls for data that rarely changes. :::note This page covers preload and caching from the formal specification (https://github.com/FlowMCP/flowmcp-spec). Preload is an optimization — tools work identically with or without caching. ::: ## Motivation Some API endpoints return complete, rarely changing datasets (e.g. all hospitals in Germany, all memorial stones in Berlin). Fetching these on every call wastes bandwidth and time. The `preload` field lets schema authors declare caching intent. ## The `preload` Field `preload` is an optional object on route level: ```javascript routes: { getLocations: { method: 'GET', path: '/locations.json', description: 'All hospital locations in Germany', parameters: [], preload: { enabled: true, ttl: 604800, description: 'All hospital locations in Germany (~760KB)' }, output: { /* ... */ }, tests: [ /* ... */ ] } } ``` ### Fields | Field | Type | Required | Description | |-------|------|----------|-------------| | `enabled` | `boolean` | Yes | Whether caching is allowed. Must be `true` to activate. | | `ttl` | `number` | Yes | Cache time-to-live in seconds. Must be a positive integer. | | `description` | `string` | No | Human-readable note shown on cache hit. | ### TTL Guidelines | TTL | Duration | Use Case | |-----|----------|----------| | `3600` | 1 hour | Frequently updated data | | `86400` | 1 day | Daily snapshots | | `604800` | 1 week | Weekly releases, semi-static data | | `2592000` | 30 days | Static reference data | ```mermaid flowchart TD A[How often does this data change?] --> B{Daily or more?} B -->|Yes| C["ttl: 3600-86400"] B -->|No| D{Weekly?} D -->|Yes| E["ttl: 604800"] D -->|No| F{Monthly or less?} F -->|Yes| G["ttl: 2592000"] F -->|No| H["Don't use preload"] ``` ## Cache Key Strategy When a route has parameters, the cache key must include parameter values: ``` # With parameters {namespace}/{routeName}/{paramHash}.json # Without parameters (or all optional and omitted) {namespace}/{routeName}.json ``` ## Runtime Behavior ### Cache Storage Recommended cache directory: `~/.flowmcp/cache/`. Each cached response is stored as JSON with metadata: ```json { "meta": { "fetchedAt": "2026-02-17T12:00:00.000Z", "expiresAt": "2026-02-24T12:00:00.000Z", "ttl": 604800, "size": 760123, "paramHash": null }, "data": { } } ``` ### Cache Flow ```mermaid flowchart TD A["flowmcp call tool-name"] --> B{"preload.enabled?"} B -->|No| C[Normal fetch] B -->|Yes| D{Cache file exists?} D -->|No| E[Fetch + store in cache] D -->|Yes| F{Cache expired?} F -->|No| G[Return cached response] F -->|Yes| E E --> H[Return fresh response] ``` ### User Overrides | Flag | Behavior | |------|----------| | `--no-cache` | Skip cache entirely, always fetch fresh | | `--refresh` | Fetch fresh and update cache | ### Cache Management | Command | Description | |---------|-------------| | `cache status` | List all cached responses with size, age, expiry | | `cache clear` | Remove all cached responses | | `cache clear {namespace}` | Remove cached responses for a specific namespace | ## When to Use Preload **Good candidates:** - The endpoint returns a complete, static or slow-changing dataset - The response is larger than ~10KB - The data doesn't change based on time-of-day or real-time events - Multiple calls with the same parameters return identical results **Bad candidates:** - The data changes frequently (live prices, real-time feeds) - The response depends on authentication state - The endpoint has rate limits that make caching counterproductive ## Interaction with Other Features ### Handlers Handlers (`preRequest`, `postRequest`) still execute on cached data. The cache stores the raw API response before `postRequest` transformation. :::tip Some runtimes may alternatively cache the final transformed response after `postRequest`, avoiding re-running handlers on every cache hit. Check your runtime's documentation for which approach is used. ::: ### Tests Route tests always bypass the cache to ensure they test the live API. The `--no-cache` flag is implied during test execution. ## Validation Rules | Code | Severity | Rule | |------|----------|------| | VAL060 | error | If `preload` is present, it must be a plain object | | VAL061 | error | `preload.enabled` must be a boolean | | VAL062 | error | `preload.ttl` must be a positive integer (> 0) | | VAL063 | warning | `preload.description` if present must be a string | | VAL064 | info | Routes with `preload.enabled: true` and no parameters are ideal cache candidates | | VAL065 | warning | Routes with `preload.enabled: true` and required parameters should document caching behavior | --- # "Prompt Architecture" /docs/specification/prompt-architecture FlowMCP v3.0.0 introduces a two-tier prompt architecture that separates model-neutral guidance from model-specific workflows. This replaces the simpler "Group Prompts" system from v2. :::note For the full specification, see 12-prompt-architecture.md (https://github.com/FlowMCP/flowmcp-spec/blob/main/spec/v3.0.0/12-prompt-architecture.md). ::: ## Two Prompt Types | Aspect | Provider-Prompt | Agent-Prompt | |--------|----------------|--------------| | **Scope** | Single namespace | Multiple providers | | **Model** | Model-neutral | Model-specific (`testedWith`) | | **Field** | `namespace` | `agent` | | **`dependsOn`** | Bare names (`getPrice`) | Full IDs (`ns/tool/name`) | | **Location** | `providers/{ns}/prompts/` | `agents/{name}/prompts/` | ## Provider-Prompt Example ```javascript export const prompt = { name: 'price-comparison', version: 'flowmcp-prompt/1.0.0', namespace: 'coingecko-com', description: 'Compare prices across multiple tokens', dependsOn: ['simplePrice', 'getCoinMarkets'], content: `Compare the prices of [[tokenList]] using...` } ``` ## Agent-Prompt Example ```javascript export const prompt = { name: 'token-deep-dive', version: 'flowmcp-prompt/1.0.0', agent: 'crypto-research', testedWith: 'anthropic/claude-sonnet-4-5-20250929', description: 'Deep token analysis across providers', dependsOn: [ 'coingecko-com/tool/simplePrice', 'etherscan/tool/getContractAbi' ], content: `Analyze [[tokenSymbol]] by first checking...` } ``` ## Placeholder Syntax Prompts use `[[...]]` placeholders for dynamic content: | Pattern | Type | Example | |---------|------|---------| | `[[name]]` (no `/`) | Parameter (user input) | `[[tokenSymbol]]`, `[[chainId]]` | | `[[ns/tool/name]]` (with `/`) | Reference (resolved via ID) | `[[coingecko-com/tool/simplePrice]]` | ## Composable References Prompts can reference other prompts via the `references[]` field (maximum 1 level deep): ```javascript export const prompt = { name: 'full-analysis', references: [ 'coingecko-com/prompt/price-comparison' ], content: `First, [[coingecko-com/prompt/price-comparison]]. Then...` } ``` ## Validation Rules | Code | Rule | |------|------| | PRM001 | `version` must be `"flowmcp-prompt/1.0.0"` | | PRM002 | Provider-Prompt must have `namespace`, must NOT have `agent` or `testedWith` | | PRM003 | Agent-Prompt must have `agent` and `testedWith`, must NOT have `namespace` | | PRM004 | `dependsOn` tools must use bare names in Provider-Prompts | | PRM005 | `dependsOn` tools must use full IDs in Agent-Prompts | | PRM006 | `references[]` limited to 1 level deep | --- # "Resources" /docs/specification/resources Resources provide fast, local read-only data access through SQLite databases. Unlike tools that make HTTP requests to external APIs, resources query local `.db` files using `sql.js` (a pure JavaScript/WASM SQLite implementation). Resources are the second MCP primitive supported by FlowMCP v3.0.0. :::note Resources are optional. Most schemas only need tools. Add resources when your schema benefits from fast local data lookups that do not require network calls. ::: ## When to Use Resources | Use Case | Tool or Resource? | |----------|-------------------| | Fetch live data from an API | Tool | | Look up static reference data (chain IDs, token lists) | Resource | | Query historical data that changes infrequently | Resource | | Perform calculations on cached data | Resource | | Call external services | Tool | Resources are ideal for data that is bundled with the schema and updated only when the schema version changes. ## Schema Format Resources are declared in the `resources` key of the `main` export: ```javascript export const main = { namespace: 'etherscan', name: 'ContractExplorer', version: '3.0.0', root: 'https://api.etherscan.io', tools: { /* ... */ }, resources: { verifiedContracts: { description: 'Lookup verified contracts by address or name', source: 'sqlite', database: 'contracts.db', queries: { byAddress: { description: 'Find a verified contract by its address', sql: 'SELECT name, compiler, optimization, source_code FROM contracts WHERE address = ?', parameters: [ { key: 'address', type: 'string', description: 'Contract address (0x...)', required: true } ] }, byName: { description: 'Search contracts by name pattern', sql: 'SELECT address, name, compiler FROM contracts WHERE name LIKE ? LIMIT 20', parameters: [ { key: 'pattern', type: 'string', description: 'Name search pattern (use % as wildcard)', required: true } ] } } } } } ``` ## Resource Fields | Field | Type | Required | Description | |-------|------|----------|-------------| | `description` | `string` | Yes | What data this resource provides. Visible to AI clients. | | `source` | `string` | Yes | Must be `'sqlite'`. Only SQLite is supported in v3.0.0. | | `database` | `string` | Yes | Path to the `.db` file, relative to the schema file. Must end in `.db`. | | `queries` | `object` | Yes | Named queries with SQL and parameters. Max 4 queries per resource. | ## Query Fields Each query is a named entry in the `queries` object: | Field | Type | Required | Description | |-------|------|----------|-------------| | `description` | `string` | Yes | What this query returns. Used by AI clients for query selection. | | `sql` | `string` | Yes | SQL query string with `?` placeholders. Must start with `SELECT`. | | `parameters` | `array` | No | Query parameters corresponding to `?` placeholders. Can be empty `[]`. | | `tests` | `array` | No | Test cases with example parameter values. | ## Query Parameters Resource query parameters use a simplified flat format (no `position`/`z` blocks): ```javascript parameters: [ { key: 'address', type: 'string', description: 'Contract address', required: true }, { key: 'limit', type: 'number', description: 'Max results', required: false } ] ``` | Field | Type | Required | Description | |-------|------|----------|-------------| | `key` | `string` | Yes | Parameter name (camelCase). Maps to a `?` placeholder in order. | | `type` | `string` | Yes | Must be `string`, `number`, or `boolean`. | | `description` | `string` | Yes | What this parameter controls. | | `required` | `boolean` | Yes | Whether the parameter must be provided. | The number of parameters must match the number of `?` placeholders in the SQL query. ## SQL Security Enforcement Resources enforce strict read-only access. The following patterns are **blocked** at validation time: | Blocked Pattern | Reason | |-----------------|--------| | `INSERT`, `UPDATE`, `DELETE`, `DROP` | Write operations not allowed | | `CREATE TABLE`, `ALTER TABLE` | Schema modifications not allowed | | `ATTACH DATABASE` | Cross-database access not allowed | | `LOAD_EXTENSION` | Extension loading not allowed | | `PRAGMA` (most) | Configuration changes not allowed | | String interpolation | All values must use `?` placeholders | | Subqueries with writes | Nested write operations not allowed | ```javascript // Valid — SELECT with prepared statement sql: 'SELECT * FROM contracts WHERE address = ?' // Valid — JOIN query sql: 'SELECT c.name, t.symbol FROM contracts c JOIN tokens t ON c.token_id = t.id WHERE c.chain_id = ?' // Invalid — not a SELECT statement sql: 'INSERT INTO contracts VALUES (?)' // Invalid — uses string interpolation instead of ? sql: `SELECT * FROM contracts WHERE address = '${address}'` // Invalid — blocked pattern sql: 'ATTACH DATABASE "other.db" AS other' ``` :::caution All SQL queries must start with `SELECT` and use `?` placeholders for all dynamic values. String interpolation, template literals, and concatenation in SQL strings are forbidden. ::: ## Runtime Resources use `sql.js`, a pure JavaScript/WASM implementation of SQLite. This means: - **No native dependencies** — works on any platform that supports WASM - **No SQLite installation required** — everything is bundled - **Read-only mode** — databases are opened in read-only mode by default - **Memory-safe** — each query runs in an isolated context ## Constraints | Constraint | Value | Rationale | |------------|-------|-----------| | Max resources per schema | 2 | Resources are supplementary, not primary output | | Max queries per resource | 4 | Keeps resource scope focused | | Source type | `sqlite` only | Future versions may add other sources | | Database file extension | `.db` | Standard SQLite extension | | SQL must start with | `SELECT` | Read-only enforcement | | Parameter placeholders | `?` only | Prevents SQL injection | | Parameter types | `string`, `number`, `boolean` | Simple types only | ## Validation Rules Resources are validated by rules RES001-RES023. Key rules include: | Code | Rule | |------|------| | RES003 | Maximum 2 resources per schema | | RES005 | Source must be `'sqlite'` | | RES006 | Database path must end in `.db` | | RES008 | Maximum 4 queries per resource | | RES012 | SQL must start with `SELECT` | | RES013 | SQL must not contain blocked patterns | | RES014 | SQL must use `?` placeholders | | RES015 | Placeholder count must match parameter count | See Validation Rules for the complete list. --- # "Tests" /docs/specification/route-tests Tests are executable examples embedded in each tool and resource definition. They serve three purposes: documenting what a tool can do, providing input data to capture real API responses, and generating accurate output schemas from those responses. :::note This page covers tests from the formal specification (https://github.com/FlowMCP/flowmcp-spec). See Output Schema for how generated schemas are used. ::: ## Purpose ```mermaid flowchart LR A[Tests] --> B[Learning: what is possible] A --> C[Execution: call real API] C --> D[Capture: store response] D --> E[Generate: output schema] ``` - **Learning instrument** — a developer or AI agent reading tests should understand the tool's capabilities by reading tests alone - **Output schema source** — tests provide parameter values for real API calls, and captured responses are fed into the `OutputSchemaGenerator` ## Tool Test Format Tests are defined as an array inside each tool definition, alongside `method`, `path`, `description`, and `parameters`: ```javascript tools: { getSimplePrice: { method: 'GET', path: '/simple/price', description: 'Fetch current price for one or more coins', parameters: [ { position: { key: 'ids', value: '{{USER_PARAM}}', location: 'query' }, z: { primitive: 'array()', options: [] } }, { position: { key: 'vs_currencies', value: '{{USER_PARAM}}', location: 'query' }, z: { primitive: 'string()', options: [] } } ], tests: [ { _description: 'Single coin in single currency', ids: ['bitcoin'], vs_currencies: 'usd' }, { _description: 'Multiple coins in multiple currencies', ids: ['bitcoin', 'ethereum', 'solana'], vs_currencies: 'usd,eur,gbp' } ] } } ``` ## Resource Query Tests Resources can also have tests. Since resources use SQL queries instead of HTTP requests, test values correspond to query parameters: ```javascript resources: { verifiedContracts: { source: 'sqlite', database: 'contracts.db', queries: { byAddress: { description: 'Find contract by address', sql: 'SELECT * FROM contracts WHERE address = ?', parameters: [ { key: 'address', type: 'string', description: 'Contract address', required: true } ], tests: [ { _description: 'Known verified contract (USDC)', address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48' } ] } } } } ``` ## Test Fields | Field | Type | Required | Description | |-------|------|----------|-------------| | `_description` | `string` | Yes | What this specific test demonstrates | | `{paramKey}` | matches parameter type | Yes (per required param) | Value for each `{{USER_PARAM}}` parameter (tools) or query parameter (resources) | ### Writing Good Descriptions ```javascript // Good — explains the specific scenario { _description: 'ERC-20 token on Ethereum mainnet', ... } { _description: 'Native token on Layer 2 chain (Arbitrum)', ... } { _description: 'Wallet with high transaction volume', ... } // Bad — generic, uninformative { _description: 'Test getTokenPrice', ... } { _description: 'Basic test', ... } ``` ## Design Principles ### 1. Express the Breadth Tests should cover the **range of what is possible**: ```javascript // Good — shows breadth of chains and token types tests: [ { _description: 'ERC-20 token on Ethereum', chain_id: 1, contract: '0x6982...' }, { _description: 'Native token on Polygon', chain_id: 137, contract: '0x0000...' }, { _description: 'Stablecoin on Arbitrum', chain_id: 42161, contract: '0xaf88...' } ] ``` ### 2. Teach Through Examples Each test should teach one capability or variation. ### 3. No Personal Data :::caution Tests must never contain personal data. All test values must be publicly known, verifiable, and non-sensitive. ::: | Allowed | Not Allowed | |---------|-------------| | Public smart contract addresses | Private wallet addresses | | Well-known token contracts (USDC, WETH) | Personal wallet addresses | | Public blockchain data (block numbers, tx hashes) | Email addresses, names | | Standard chain IDs (1, 137, 42161) | API keys, tokens, passwords | ### 4. Reproducible Results Prefer well-established tokens/contracts over newly deployed ones, and historical data queries over latest-block queries when possible. ## Test Count Guidelines | Scenario | Minimum | Recommended | |----------|---------|-------------| | No parameters | 1 | 1 | | 1-2 parameters | 1 | 2-3 | | Enum/chain parameters | 1 | 2-4 (different enum values) | | Multiple optional parameters | 1 | 2-3 (with/without optionals) | | Resource queries | 1 | 1-2 | Minimum 1 test per tool is required (validation error TST001 if missing). ## Response Capture Lifecycle ```mermaid flowchart TD A[1. Read tests from tool] --> B[2. Resolve server params from env] B --> C[3. Execute API call per test] C --> D[4. Record full response with metadata] D --> E[5. Feed response to OutputSchemaGenerator] E --> F[6. Write output schema to tool definition] ``` 1. **Read Tests** — Extract parameter values from each test object. 2. **Resolve Server Params** — Load `requiredServerParams` from environment variables. 3. **Execute API Call** — Construct the full request and execute. A delay between calls (default: 1s) prevents rate limiting. 4. **Record Response** — Store full response with metadata (namespace, toolName, testIndex, timestamp, responseTime). 5. **Generate Output Schema** — The `OutputSchemaGenerator` analyzes `response.data` structure and produces a schema definition. 6. **Write Output Schema** — Generated schema is written to the tool's `output` field. Existing schemas can be validated against reality. ### Captured Response Format ```javascript { namespace: 'coingecko', toolName: 'getSimplePrice', testIndex: 0, _description: 'Single coin in USD', userParams: { ids: ['bitcoin'], vs_currencies: 'usd' }, responseTime: 234, timestamp: '2026-02-17T10:30:00Z', response: { status: true, messages: [], data: { /* actual API response after handler transformation */ } } } ``` :::tip The `response.data` contains data **after handler transformation**. This is critical because the output schema describes the final shape, not the raw API response. ::: ### Capture File Structure ``` capture/ └── {timestamp}/ └── {namespace}/ ├── {toolName}-0.json ├── {toolName}-1.json └── metrics.json ``` ## Test Execution Modes | Mode | Description | Use Case | |------|-------------|----------| | **Capture** | Execute against real API, store responses | Schema development, output schema generation | | **Validation** | Execute and compare against declared `output.schema` | Verify schemas remain accurate over time | | **Dry-Run** | Validate test definitions without API calls | During `flowmcp validate` | ## Validation Rules | Code | Severity | Rule | |------|----------|------| | TST001 | error | Each tool must have at least 1 test | | TST002 | error | Each test must have a `_description` field of type string | | TST003 | error | Each test must provide values for all required `{{USER_PARAM}}` parameters | | TST004 | error | Test parameter values must pass the corresponding `z` validation | | TST005 | error | Test objects must be JSON-serializable | | TST006 | error | Test objects must only contain keys matching `{{USER_PARAM}}` parameter keys or `_description` | | TST007 | warning | Tools with enum parameters should test multiple enum values | | TST008 | info | Consider adding tests that demonstrate optional parameter usage | --- # "Schema Format" /docs/specification/schema-format A FlowMCP schema is a `.mjs` file with **two separate named exports**: a static `main` block and an optional `handlers` factory function. This separation enables integrity hashing, security scanning, and dependency injection. :::note This page covers the schema format from the formal specification (https://github.com/FlowMCP/flowmcp-spec). See Parameters for parameter details and Security Model for security constraints. ::: ## The Two-Export Pattern ```javascript // main export (required) // Static, declarative, JSON-serializable — hashable without execution export const main = { namespace: 'provider', name: 'SchemaName', description: 'What this schema does', version: '3.0.0', root: 'https://api.provider.com', tools: { /* ... */ }, resources: { /* ... */ }, // optional skills: [ /* ... */ ] // optional } // handlers export (optional) // Factory function — receives injected dependencies export const handlers = ( { sharedLists, libraries } ) => ({ toolName: { preRequest: async ( { struct, payload } ) => { return { struct, payload } }, postRequest: async ( { response, struct, payload } ) => { return { response } } } }) ``` ### Why Two Separate Exports - **`main` can be hashed without calling any function.** The runtime reads the static export, serializes it via `JSON.stringify()`, and computes its hash. - **Handlers receive all dependencies through injection.** Schema files have zero `import` statements. - **`requiredLibraries` declares what npm packages the schema needs.** The runtime loads them from a security allowlist and injects them. ## The `main` Export All fields in `main` must be JSON-serializable. No functions, no dynamic values, no imports. ### Required Fields | Field | Type | Description | |-------|------|-------------| | `namespace` | `string` | Provider identifier, lowercase letters only (`/^[a-z]+$/`). | | `name` | `string` | Schema name in PascalCase (e.g. `SmartContractExplorer`). | | `description` | `string` | What this schema does, 1-2 sentences. | | `version` | `string` | Must match `3.\d+.\d+` (semver, major must be `3`). | | `root` | `string` | Base URL for all tools. Must start with `https://` (no trailing slash). | | `tools` | `object` | Tool definitions. Keys are camelCase tool names. Maximum 8 tools. | ### Optional Fields | Field | Type | Default | Description | |-------|------|---------|-------------| | `docs` | `string[]` | `[]` | Documentation URLs for the API provider. | | `tags` | `string[]` | `[]` | Categorization tags for tool discovery. | | `requiredServerParams` | `string[]` | `[]` | Environment variable names needed at runtime. | | `requiredLibraries` | `string[]` | `[]` | npm packages needed by handlers (must be on allowlist). | | `headers` | `object` | `{}` | Default HTTP headers applied to all tools. | | `sharedLists` | `object[]` | `[]` | Shared list references. See Shared Lists. | | `resources` | `object` | `{}` | SQLite-based read-only data resources. See Resources. | | `skills` | `array` | `[]` | AI agent skill references. See Skills. | :::note The key `routes` is accepted as a deprecated alias for `tools`. Schemas using `routes` will work in v3.0.0 but will produce warnings in v3.1.0 and errors in v3.2.0. Use `flowmcp migrate` to update automatically. ::: ### Field Details #### `namespace` Only lowercase ASCII letters. No numbers, hyphens, or underscores: ```javascript // Valid namespace: 'etherscan' namespace: 'coingecko' // Invalid namespace: 'defi-llama' // hyphen not allowed namespace: 'CoinGecko' // uppercase not allowed ``` #### `root` Must use HTTPS with no trailing slash: ```javascript // Valid root: 'https://api.etherscan.io' root: 'https://pro-api.coingecko.com/api/v3' // Invalid root: 'http://api.etherscan.io' // must be HTTPS root: 'https://api.etherscan.io/' // no trailing slash ``` ## Tool Definition Each key in `tools` is the tool name in camelCase. ### Tool Fields | Field | Type | Required | Description | |-------|------|----------|-------------| | `method` | `string` | Yes | HTTP method: `GET`, `POST`, `PUT`, `DELETE`. | | `path` | `string` | Yes | URL path appended to `root`. May contain `{{key}}` placeholders. | | `description` | `string` | Yes | What this tool does. Appears in tool description. | | `parameters` | `array` | Yes | Input parameter definitions. Can be empty `[]`. | | `tests` | `array` | Yes | Executable test cases. At least 1 per tool. | | `output` | `object` | No | Output schema. See Output Schema. | | `preload` | `object` | No | Cache configuration. See Preload. | ### Path Templates The path supports `{{key}}` placeholders that are replaced by `insert` parameters: ```javascript // Static path path: '/api' // Single placeholder path: '/api/v1/{{address}}/transactions' // Multiple placeholders path: '/api/v1/{{chainId}}/address/{{address}}/balances' ``` ## The `handlers` Export The `handlers` export is a factory function receiving injected dependencies: ```javascript export const handlers = ( { sharedLists, libraries } ) => { const { ethers } = libraries return { getContractAbi: { preRequest: async ( { struct, payload } ) => { const checksummed = ethers.getAddress( payload.address ) return { struct, payload: { ...payload, address: checksummed } } } } } } ``` ### Handler Types | Handler | When | Input | Must Return | |---------|------|-------|-------------| | `preRequest` | Before the API call | `{ struct, payload }` | `{ struct, payload }` | | `postRequest` | After the API call | `{ response, struct, payload }` | `{ response }` | ### Handler Rules 1. **Handlers are optional.** Tools without handlers make direct API calls. 2. **Zero import statements.** All dependencies are injected through the factory function. 3. **No restricted globals.** `fetch`, `fs`, `process`, `eval`, `Function`, `setTimeout` are forbidden. 4. **`sharedLists` is read-only.** Deep-frozen via `Object.freeze()`. Mutations throw `TypeError`. 5. **Handlers must be pure transformations.** No side effects, no state mutations, no logging. ## Runtime Loading Sequence ```mermaid flowchart TD A[Read schema file as string] --> B[Static security scan] B --> C[Dynamic import] C --> D[Extract main export] D --> E[Validate main block] E --> F[Resolve sharedLists] F --> G[Load requiredLibraries from allowlist] G --> H{handlers export exists?} H -->|Yes| I["Call handlers( { sharedLists, libraries } )"] H -->|No| J[Direct API call mode] I --> K[Register tools as MCP tools] J --> K K --> L{resources defined?} L -->|Yes| M[Load SQLite databases] L -->|No| N{skills defined?} M --> N N -->|Yes| O[Load skill .mjs files] N -->|No| P[Ready] O --> P ``` ## Naming Conventions | Element | Convention | Pattern | Example | |---------|-----------|---------|---------| | Namespace | Lowercase letters only | `^[a-z]+$` | `etherscan` | | Schema name | PascalCase | `^[A-Z][a-zA-Z0-9]*$` | `SmartContractExplorer` | | Schema filename | PascalCase `.mjs` | `^[A-Z][a-zA-Z0-9]*\.mjs$` | `SmartContractExplorer.mjs` | | Tool name | camelCase | `^[a-z][a-zA-Z0-9]*$` | `getContractAbi` | | Parameter key | camelCase | `^[a-z][a-zA-Z0-9]*$` | `contractAddress` | | Tag | lowercase with hyphens | `^[a-z][a-z0-9-]*$` | `smart-contracts` | ## Constraints | Constraint | Value | Rationale | |------------|-------|-----------| | Max tools per schema | 8 | Keeps schemas focused. Split large APIs into multiple schemas. | | Max resources per schema | 2 | Resources are supplementary data, not primary output. | | Max skills per schema | 4 | Skills compose tools; keep schemas focused. | | Version major | `3` | Must match `3.\d+.\d+`. | | Namespace pattern | `^[a-z]+$` | Letters only. No numbers, hyphens, or underscores. | | Root URL protocol | `https://` | HTTP is not allowed. | | `main` export | JSON-serializable | Must survive `JSON.parse( JSON.stringify() )` roundtrip. | | Schema file imports | Zero | All dependencies are injected. | ## Complete Example ```javascript export const main = { namespace: 'etherscan', name: 'SmartContractExplorer', description: 'Explore verified smart contracts on EVM-compatible chains via Etherscan APIs', version: '3.0.0', root: 'https://api.etherscan.io', docs: [ 'https://docs.etherscan.io/api-endpoints/contracts' ], tags: [ 'smart-contracts', 'evm', 'abi' ], requiredServerParams: [ 'ETHERSCAN_API_KEY' ], requiredLibraries: [], headers: { 'Accept': 'application/json' }, sharedLists: [ { ref: 'evmChains', version: '1.0.0', filter: { key: 'etherscanAlias', exists: true } } ], tools: { getContractAbi: { method: 'GET', path: '/api', description: 'Returns the Contract ABI of a verified smart contract', parameters: [ { position: { key: 'module', value: 'contract', location: 'query' }, z: { primitive: 'string()', options: [] } }, { position: { key: 'action', value: 'getabi', location: 'query' }, z: { primitive: 'string()', options: [] } }, { position: { key: 'address', value: '{{USER_PARAM}}', location: 'query' }, z: { primitive: 'string()', options: [ 'min(42)', 'max(42)' ] } }, { position: { key: 'apikey', value: '{{SERVER_PARAM:ETHERSCAN_API_KEY}}', location: 'query' }, z: { primitive: 'string()', options: [] } } ] } } } export const handlers = ( { sharedLists } ) => ({ getContractAbi: { postRequest: async ( { response } ) => { const [ first ] = response.result return { response: { contractName: first.ContractName, sourceCode: first.SourceCode } } } } }) ``` :::tip This example demonstrates: two separate exports, fixed parameters, user parameters, server parameter injection, a shared list reference, and a `postRequest` handler that flattens the response. ::: --- # "Security Model" /docs/specification/security FlowMCP enforces a layered security model that prevents schema files from accessing the network, filesystem, or process environment. All potentially dangerous operations are restricted to the trusted core runtime. Dependencies are injected through a factory function pattern, and external libraries are gated by an allowlist. :::note This page covers the security model from the formal specification (https://github.com/FlowMCP/flowmcp-spec). Security is enforced at multiple levels — static scan, runtime constraints, and API key isolation. ::: ## Trust Boundary FlowMCP enforces a strict trust boundary between the core runtime and schema handlers: ```mermaid flowchart LR subgraph trusted ["TRUSTED ZONE - flowmcp-core"] A[Static scan] B[Validate main block] C[Resolve shared lists] D[Load libraries from allowlist] E[Execute fetch] F[Validate input/output] end subgraph restricted ["RESTRICTED ZONE - Schema Handlers"] G["Receives: struct, payload"] H["Receives: sharedLists, libraries"] I["sharedLists is frozen"] K["NO: import, require, eval, fs, process"] L["CAN: use injected libs and sharedLists"] end A --> B --> C --> D --> E --> F D --> H E --> G ``` **Trusted Zone (flowmcp-core):** - Reads schema file as raw string and runs static security scan - Loads and validates the `main` export - Resolves shared list references and deep-freezes the data - Loads libraries from the allowlist - Executes HTTP fetch (handlers never fetch directly) - Validates input parameters and output schema **Restricted Zone (schema handlers):** - Receives `sharedLists` and `libraries` through the factory function - Receives `struct`, `payload`, and `response` per-call - Transforms data (restructure, filter, compute) - Cannot access network, filesystem, process, or global scope ## Static Security Scan Before a schema is loaded, the **raw file content** is scanned for forbidden patterns. This happens before `import()` to prevent code execution. :::caution Since all dependencies are injected through the factory function, schema files must have **zero import statements**. Any occurrence of `import ` in the file content causes rejection. ::: ### Forbidden Patterns | Pattern | Reason | |---------|--------| | `import ` | No imports — all dependencies are injected | | `require(` | No CommonJS imports | | `eval(` | Code injection | | `Function(` | Code injection | | `new Function` | Code injection | | `fs.` | Filesystem access | | `node:fs` | Filesystem access | | `fs/promises` | Filesystem access | | `process.` | Process access | | `child_process` | Shell execution | | `globalThis.` | Global scope access | | `global.` | Global scope access | | `__dirname` | Path leaking | | `__filename` | Path leaking | | `setTimeout` | Async side effects | | `setInterval` | Async side effects | ### Scan Sequence ``` 1. Read file as raw string (before import) 2. Scan entire file for all forbidden patterns 3. If any pattern matches -> reject file with error message(s) 4. If clean -> proceed with dynamic import() ``` The entire file is scanned uniformly — no distinction between main and handler regions. ## Library Allowlist The runtime maintains an allowlist of approved npm packages. Only these packages can be declared in `requiredLibraries` and injected into handlers. ### Default Allowlist ```javascript const DEFAULT_ALLOWLIST = [ 'ethers', 'moment', 'indicatorts', '@erc725/erc725.js', 'ccxt', 'axios' ] ``` ### User-Extended Allowlist Users can extend the allowlist in `.flowmcp/config.json`: ```json { "security": { "allowedLibraries": [ "custom-lib", "another-lib" ] } } ``` The effective allowlist is the union of default and user-extended lists. ### Library Loading Sequence ```mermaid flowchart TD A[Read main.requiredLibraries] --> B{Each library on allowlist?} B -->|Yes| C["Load via dynamic import()"] B -->|No| D[Reject schema - SEC013] C --> E[Package into libraries object] E --> F["Inject into handlers()"] ``` 1. **Read requiredLibraries** — Extract the list of declared packages from the main block. 2. **Check allowlist** — Every entry must appear in the default or user-extended allowlist. 3. **Load approved libraries** — Each approved library is loaded via dynamic `import()`. 4. **Inject into factory** — The `libraries` object is passed to `handlers( { sharedLists, libraries } )`. ## Shared List File Restrictions Shared list files have an **even stricter** scan: | Allowed | Forbidden | |---------|-----------| | `export const list = { meta: {...}, entries: [...] }` | Any function definition | | String/number/boolean/null values | `async`, `await`, `function`, `=>` | | Arrays and objects | Any schema forbidden patterns | | Comments (`//`, `/* */`) | Template literals with expressions | Shared lists are pure data. No logic, no transformations, no computed values. ## Handler Runtime Constraints Even after passing the static scan, handlers are constrained: 1. **No `fetch` access** — the runtime executes fetch and passes the response to `postRequest` 2. **No side effects** — receive data, return data. No logging, no file writes 3. **`sharedLists` is read-only** — deep-frozen via `Object.freeze()`. Mutations throw `TypeError` 4. **Only allowlisted packages** — non-injected packages are not in scope 5. **Return value required** — must return the expected shape ### Handler Function Signatures ```javascript // preRequest — modify the request before fetch preRequest: async ( { struct, payload } ) => { // struct: the request structure (url, headers, body) // payload: resolved route parameters // sharedLists + libraries: available via factory closure return { struct, payload } } // postRequest — transform the response after fetch postRequest: async ( { response, struct, payload } ) => { // response: parsed JSON from the API // sharedLists + libraries: available via factory closure return { response } } ``` ## API Key Protection :::caution API keys are **never exposed to handler code**. They are injected by the runtime into URL/headers only. ::: ```mermaid flowchart LR A[".env: ETHERSCAN_API_KEY=abc123"] --> B[Core Runtime] B --> C["URL: ?apikey=abc123"] B -.->|NOT passed| D[Handler Factory] B -.->|NOT passed| F[Handler Per-Call] ``` - `requiredServerParams` values are injected into URL/headers by the runtime - The `handlers()` factory receives `sharedLists` and `libraries` only - Per-call handlers receive `struct`, `payload`, and `response` only - Key values are never logged ### Key Injection Flow ``` 1. Schema declares requiredServerParams: [ 'ETHERSCAN_API_KEY' ] 2. Runtime reads ETHERSCAN_API_KEY from .env 3. Parameter template: '{{SERVER_PARAM:ETHERSCAN_API_KEY}}' 4. Runtime substitutes into URL: '?apikey=abc123' 5. Handler receives response — never sees the key value ``` ## Threat Model | Threat | Mitigation | |--------|------------| | Schema imports a module | Static scan blocks `import`/`require` | | Schema requests unapproved library | Blocked by allowlist (SEC013) | | Schema reads filesystem | Static scan blocks `fs`, `node:fs` | | Schema executes shell commands | Static scan blocks `child_process` | | Schema accesses environment | Static scan blocks `process.` | | Schema exfiltrates data via fetch | Handlers cannot call `fetch()` | | Schema modifies global state | Static scan blocks `globalThis`/`global.` | | Handler mutates shared list data | `sharedLists` is deep-frozen | | Shared list contains executable code | Stricter scan blocks all functions/arrows | | Schema leaks API keys | Keys never passed to factory or handlers | | Schema uses eval | Static scan blocks `eval(`, `Function(` | ## Security Error Codes ### SEC001-SEC099 — Static Scan Failures | Code | Description | |------|-------------| | SEC001 | Forbidden `import` statement found | | SEC002 | Forbidden `require()` call found | | SEC003 | Forbidden `eval()` call found | | SEC004 | Forbidden `Function()` constructor found | | SEC005 | Forbidden filesystem access | | SEC006 | Forbidden `process.` access | | SEC007 | Forbidden `child_process` access | | SEC008 | Forbidden global scope access | | SEC009 | Forbidden path variable | | SEC010 | Forbidden `new Function` | | SEC011 | Forbidden timer (`setTimeout`/`setInterval`) | | SEC013 | Unapproved library in `requiredLibraries` | ### SEC100-SEC199 — Runtime Constraint Violations | Code | Description | |------|-------------| | SEC100 | Handler attempted to call `fetch()` | | SEC101 | Handler returned invalid shape | | SEC102 | Handler attempted to mutate frozen `sharedLists` | | SEC103 | Library loading failed for approved package | | SEC104 | Factory function `handlers()` threw during initialization | ### SEC200-SEC299 — Shared List Scan Failures | Code | Description | |------|-------------| | SEC200 | Function definition found in shared list | | SEC201 | Arrow function found in shared list | | SEC202 | Async/await keyword found in shared list | | SEC203 | Template literal with expression found | | SEC204 | Forbidden pattern found in shared list | All violations in a single file are reported together — the scan does not stop at the first match. --- # "Shared Lists" /docs/specification/shared-lists Shared lists eliminate duplication of common value sets across schemas. Instead of every Etherscan schema maintaining its own chain list, they reference a single `evmChains` shared list that is injected at load-time. :::note This page covers shared lists from the formal specification (https://github.com/FlowMCP/flowmcp-spec). See Parameters for how schemas use shared list interpolation. ::: ## Purpose Many schemas across different providers need the same sets of values — EVM chain identifiers, fiat currency codes, token standards. Without shared lists, each schema duplicates these values inline, leading to: - **Inconsistency** — one schema uses `'eth'`, another uses `'ETH'`, a third uses `'ethereum'` - **Maintenance burden** — adding a new chain means updating dozens of schemas - **No single source of truth** — no way to verify which values are canonical ```mermaid flowchart LR A[Shared List: evmChains] --> B[etherscan/contracts.mjs] A --> C[moralis/tokens.mjs] A --> D[defillama/protocols.mjs] B --> E["filter: etherscanAlias exists"] C --> F["filter: moralisChainSlug exists"] D --> G["filter: defillamaSlug exists"] ``` A single shared list feeds multiple schemas. Each schema applies its own filter to extract only the entries relevant to that provider. ## List Definition Format A shared list is a `.mjs` file that exports a `list` object with two top-level keys: `meta` and `entries`. ```javascript export const list = { meta: { name: 'evmChains', version: '1.0.0', description: 'Unified EVM chain registry with provider-specific aliases', fields: [ { key: 'alias', type: 'string', description: 'Canonical chain alias' }, { key: 'chainId', type: 'number', description: 'EVM chain ID' }, { key: 'name', type: 'string', description: 'Human-readable chain name' }, { key: 'etherscanAlias', type: 'string', optional: true, description: 'Etherscan API chain parameter' }, { key: 'moralisChainSlug', type: 'string', optional: true, description: 'Moralis chain slug' }, { key: 'defillamaSlug', type: 'string', optional: true, description: 'DeFi Llama chain identifier' }, { key: 'coingeckoPlatformId', type: 'string', optional: true, description: 'CoinGecko asset platform ID' } ], dependsOn: [] }, entries: [ { alias: 'ETHEREUM_MAINNET', chainId: 1, name: 'Ethereum Mainnet', etherscanAlias: 'ETH', moralisChainSlug: 'eth', defillamaSlug: 'Ethereum', coingeckoPlatformId: 'ethereum' }, { alias: 'POLYGON_MAINNET', chainId: 137, name: 'Polygon Mainnet', etherscanAlias: 'POLYGON', moralisChainSlug: 'polygon', defillamaSlug: 'Polygon', coingeckoPlatformId: 'polygon-pos' } // ... more entries ] } ``` :::caution The file must export exactly one `list` constant. No other exports, no imports, no function definitions, no dynamic expressions. Shared lists are pure data. ::: ## Meta Block The `meta` block describes the list identity and structure. | Field | Type | Required | Description | |-------|------|----------|-------------| | `name` | `string` | Yes | Unique list identifier (camelCase) | | `version` | `string` | Yes | Semver version | | `description` | `string` | Yes | What this list contains | | `fields` | `array` | Yes | Field definitions for entries | | `dependsOn` | `array` | No | Dependencies on other lists | ### Naming Convention List names use camelCase and must be globally unique. The name should describe the collection, not a single entry: - `evmChains` (not `evmChain`) - `fiatCurrencies` (not `fiatCurrency`) - `isoCountryCodes` (not `countryCode`) ### Versioning Lists follow strict semver: | Bump | When | |------|------| | **Patch** (`1.0.1`) | Correcting a typo, fixing a wrong value | | **Minor** (`1.1.0`) | Adding new entries, adding new optional fields | | **Major** (`2.0.0`) | Removing entries, removing fields, renaming fields, changing types | Schemas pin to a specific version. A major version bump requires all referencing schemas to update. ## Field Definitions Each entry in `meta.fields` describes one field that entries can or must contain. | Field | Type | Required | Description | |-------|------|----------|-------------| | `key` | `string` | Yes | Field name | | `type` | `string` | Yes | `string`, `number`, or `boolean` | | `description` | `string` | Yes | What this field represents | | `optional` | `boolean` | No | If `true`, entries may omit this field | Only three primitive types are supported — no objects, arrays, or nested structures. Lists are flat by design. ### Required vs Optional Fields Fields without `optional: true` are required in every entry. Optional fields may be omitted or set to `null`. This enables provider-specific columns — `etherscanAlias` is optional because not every chain has an Etherscan explorer. ## Dependencies Between Lists Lists can declare dependencies on other lists using `meta.dependsOn`: ```javascript meta: { name: 'germanBundeslaender', version: '1.0.0', description: 'German federal states', fields: [ { key: 'name', type: 'string', description: 'State name' }, { key: 'code', type: 'string', description: 'State code' }, { key: 'countryRef', type: 'string', description: 'Reference to parent country' } ], dependsOn: [ { ref: 'isoCountryCodes', version: '1.0.0', condition: { field: 'alpha2', value: 'DE' } } ] } ``` ### Dependency Rules 1. **`ref` must resolve** — the referenced list name must exist in the registry 2. **Version pinning** — `version` pins the dependency to a specific semver version 3. **`condition` is optional** — when present, it filters the parent list 4. **No circular dependencies** — A depends on B means B cannot depend on A 5. **Maximum depth: 3 levels** — prevents resolution complexity ```mermaid flowchart TD A[isoCountryCodes] --> B[germanBundeslaender] A --> C[usStates] B --> D[germanLandkreise] D -.->|"FORBIDDEN: depth 4"| E[germanGemeinden] ``` ## Referencing from Schemas Schemas reference shared lists in the `main.sharedLists` array: ```javascript main: { sharedLists: [ { ref: 'evmChains', version: '1.0.0', filter: { key: 'etherscanAlias', exists: true } } ] } ``` ### Reference Fields | Field | Type | Required | Description | |-------|------|----------|-------------| | `ref` | `string` | Yes | List name to reference | | `version` | `string` | Yes | Required list version | | `filter` | `object` | No | Filter entries before injection | ### Filter Types | Filter | Description | Example | |--------|-------------|---------| | `{ key, exists: true }` | Only entries where field exists and is not `null` | `{ key: 'etherscanAlias', exists: true }` | | `{ key, value }` | Only entries where field equals value | `{ key: 'mainnet', value: true }` | | `{ key, in: [...] }` | Only entries where field is in the list | `{ key: 'chainId', in: [1, 137, 42161] }` | When `filter` is omitted, all entries are injected. ## Runtime Injection Lifecycle ```mermaid flowchart TD A[Schema declares sharedLists] --> B[Resolve list by name + version] B --> C{List found?} C -->|No| D[Load error: list not found] C -->|Yes| E{Version matches?} E -->|No| F[Load error: version mismatch] E -->|Yes| G[Apply filter] G --> H[Inject into sharedLists for factory] H --> I[Build reverse-lookup index] ``` 1. **Resolve** — The runtime looks up each `ref` in the list registry. If not found, the schema fails to load. 2. **Version Check** — The runtime verifies the registry version matches. A mismatch is a hard error. 3. **Filter** — If a `filter` is declared, the runtime applies it. Otherwise all entries pass through. 4. **Inject** — Filtered entries are passed to the `handlers` factory as `sharedLists`: ```javascript export const handlers = ( { sharedLists } ) => ({ getGasOracle: { preRequest: async ( { struct, payload } ) => { const chain = sharedLists.evmChains .find( ( entry ) => entry.etherscanAlias === payload.chainName ) return { struct, payload } } } }) ``` ## List Registry All shared lists are tracked in `_lists/_registry.json`: ```json { "specVersion": "2.0.0", "lists": [ { "name": "evmChains", "version": "1.0.0", "file": "_lists/evm-chains.mjs", "entryCount": 15, "hash": "sha256:def456..." } ] } ``` ### Registry Invariants - Every `.mjs` file in `_lists/` must have a registry entry - Every registry entry must point to an existing file - The `hash` must match the current file content - Validate with `flowmcp validate-lists` ## File Conventions ``` _lists/ _registry.json evm-chains.mjs fiat-currencies.mjs iso-country-codes.mjs ``` - File names use kebab-case: `evm-chains.mjs` - List names use camelCase: `evmChains` - One list per file --- # "Skills" /docs/specification/skills Skills are reusable AI agent instructions that compose tools and resources into multi-step workflows. They map to the MCP **Prompts** primitive and are declared in the `skills` key of a schema's `main` export. Each skill is stored as a separate `.mjs` file alongside the schema. :::note Skills are optional. Most schemas only need tools. Add skills when your schema has tools that work together in a predictable workflow that benefits from step-by-step guidance. ::: ## When to Use Skills | Use Case | Skill Needed? | |----------|---------------| | Single tool call (get price, check status) | No | | Multi-step workflow (fetch data, transform, report) | Yes | | Common combination of tools that agents should know about | Yes | | Simple tool with clear description | No | Skills are most valuable when: - Multiple tools from the schema work together in a specific sequence - The workflow requires context about how to interpret intermediate results - AI agents would benefit from structured guidance on tool composition ## Schema Declaration Skills are referenced in the `main` export's `skills` array: ```javascript export const main = { namespace: 'etherscan', name: 'ContractExplorer', version: '3.0.0', root: 'https://api.etherscan.io', tools: { getContractAbi: { /* ... */ }, getSourceCode: { /* ... */ } }, skills: [ { name: 'full-contract-audit', file: 'full-contract-audit.mjs', description: 'Retrieve ABI and source code for a comprehensive contract audit' } ] } ``` ### Skill Reference Fields | Field | Type | Required | Description | |-------|------|----------|-------------| | `name` | `string` | Yes | Skill identifier. Must match `^[a-z][a-z0-9-]*$` (lowercase, hyphens). | | `file` | `string` | Yes | Path to the `.mjs` skill file, relative to the schema. Must end in `.mjs`. | | `description` | `string` | Yes | What this skill does. Visible to AI clients. | ## Skill File Format Each skill is a `.mjs` file that exports a `skill` object: ```javascript const content = ` ## Instructions You are performing a comprehensive smart contract audit. ### Step 1: Retrieve ABI Call {{tool:getContractAbi}} with the provided {{input:address}}. Parse the ABI to identify all public functions, events, and modifiers. ### Step 2: Retrieve Source Code Call {{tool:getSourceCode}} with the same {{input:address}}. Analyze the Solidity source for: - Reentrancy vulnerabilities - Access control patterns - Gas optimization opportunities ### Step 3: Cross-Reference Compare the ABI with the source code to verify: - All public functions are documented - Event emissions match expected patterns - Modifier usage is consistent ### Step 4: Report Produce a Markdown report with: - Function summary table - Security findings (Critical / Warning / Info) - Gas optimization suggestions ` export const skill = { name: 'full-contract-audit', version: 'flowmcp-skill/1.0.0', description: 'Retrieve ABI and source code for a comprehensive contract audit.', requires: { tools: [ 'getContractAbi', 'getSourceCode' ], resources: [], external: [] }, input: [ { key: 'address', type: 'string', description: 'Ethereum contract address (0x...)', required: true } ], output: 'Markdown report with ABI summary, security findings, and optimization suggestions.', content } ``` ## Skill Object Fields | Field | Type | Required | Description | |-------|------|----------|-------------| | `name` | `string` | Yes | Must match the `name` in the schema's `skills` array entry. | | `version` | `string` | Yes | Must be `'flowmcp-skill/1.0.0'`. | | `description` | `string` | Yes | What this skill does. | | `requires` | `object` | Yes | Dependencies: `tools`, `resources`, and `external` arrays. | | `input` | `array` | No | User-provided input parameters. | | `output` | `string` | No | Description of what the skill produces. | | `content` | `string` | Yes | The instruction text with placeholders. | ### `requires` Object | Field | Type | Description | |-------|------|-------------| | `tools` | `string[]` | Tool names from the schema that this skill uses. Must match tool names in `main.tools`. | | `resources` | `string[]` | Resource names from the schema that this skill uses. Must match names in `main.resources`. | | `external` | `string[]` | External capabilities not provided by the schema (for documentation purposes). | ### `input` Array Each input parameter: | Field | Type | Description | |-------|------|-------------| | `key` | `string` | Parameter name (camelCase). Referenced in content as `{{input:key}}`. | | `type` | `string` | Must be `string`, `number`, or `boolean`. | | `description` | `string` | What this input parameter is for. | | `required` | `boolean` | Whether the user must provide this value. | ## Placeholder System The `content` field supports four types of placeholders: | Placeholder | Syntax | Resolves To | Example | |-------------|--------|-------------|---------| | Tool reference | `{{tool:name}}` | Tool name from `requires.tools` | `{{tool:getContractAbi}}` | | Resource reference | `{{resource:name}}` | Resource name from `requires.resources` | `{{resource:verifiedContracts}}` | | Skill reference | `{{skill:name}}` | Another skill in the same schema | `{{skill:quick-check}}` | | Input reference | `{{input:key}}` | User-provided input value | `{{input:address}}` | ### Placeholder Rules 1. `{{tool:x}}` — `x` should be listed in `requires.tools` 2. `{{resource:x}}` — `x` should be listed in `requires.resources` 3. `{{skill:x}}` — `x` must reference another skill in the same schema (no circular references) 4. `{{input:x}}` — `x` should match an `input[].key` Unresolved placeholders produce validation warnings (not errors), except for `{{skill:x}}` which must resolve. ## Versioning All skills use the version string `'flowmcp-skill/1.0.0'`. This version identifies the skill format, not the skill's content version. When the skill format changes, this version will be updated. ```javascript version: 'flowmcp-skill/1.0.0' ``` ## Constraints | Constraint | Value | Rationale | |------------|-------|-----------| | Max skills per schema | 4 | Skills compose tools; keep schemas focused | | Skill name pattern | `^[a-z][a-z0-9-]*$` | Lowercase with hyphens | | Skill file extension | `.mjs` | ES module format | | Version | `flowmcp-skill/1.0.0` | Fixed for v3.0.0 | | Content | Non-empty string | Must contain instructions | | No circular references | Via `{{skill:x}}` | Prevents infinite loops | ## Complete Example A schema with tools, a resource, and a skill: ```javascript // etherscan-contracts.mjs export const main = { namespace: 'etherscan', name: 'ContractExplorer', description: 'Explore verified smart contracts with API tools and local data', version: '3.0.0', root: 'https://api.etherscan.io', requiredServerParams: [ 'ETHERSCAN_API_KEY' ], requiredLibraries: [], headers: {}, tools: { getContractAbi: { method: 'GET', path: '/api', description: 'Returns the ABI of a verified smart contract', parameters: [ /* ... */ ] }, getSourceCode: { method: 'GET', path: '/api', description: 'Returns the Solidity source code of a verified contract', parameters: [ /* ... */ ] } }, skills: [ { name: 'full-contract-audit', file: 'full-contract-audit.mjs', description: 'Comprehensive contract audit using ABI, source code, and local metadata' } ] } ``` ## Validation Rules Skills are validated by rules SKL001-SKL025. Key rules include: | Code | Rule | |------|------| | SKL002 | Maximum 4 skills per schema | | SKL005 | Skill file must end in `.mjs` | | SKL008 | `skill.name` must match the name in `main.skills` entry | | SKL009 | Version must be `'flowmcp-skill/1.0.0'` | | SKL013 | `requires.tools` entries must match tool names in schema | | SKL014 | `requires.resources` entries must match resource names in schema | | SKL025 | No circular skill references via `{{skill:x}}` placeholders | See Validation Rules for the complete list. ## Group-Level Skills vs Schema-Level Skills FlowMCP has two types of skills: | Type | Location | Format | Purpose | |------|----------|--------|---------| | **Schema-level** | `.mjs` files alongside schema | `export const skill` object | Distributed with the schema, composing that schema's tools | | **Group-level** | `.md` files in `.flowmcp/skills/` | Markdown with sections | Project-specific workflows across multiple schemas | Schema-level skills (this page) are part of the schema and travel with it. Group-level skills (see Groups & Skills) are project-local and can reference tools from any schema in the group. --- # "Tests" /docs/specification/tests FlowMCP v3.0.0 requires a minimum of 3 tests per tool, resource query, and agent. Tests serve as both validation and documentation of expected behavior. :::note For the full specification, see 10-tests.md (https://github.com/FlowMCP/flowmcp-spec/blob/main/spec/v3.0.0/10-tests.md). ::: ## Tool Tests Each tool in `main.tools` must have at least 3 test entries: ```javascript tools: { getPrice: { method: 'GET', path: '/simple/price', description: 'Get token price', parameters: [...], tests: [ { _description: 'Basic price lookup', vs_currencies: 'usd', ids: 'bitcoin' }, { _description: 'Multi-token query', vs_currencies: 'usd', ids: 'bitcoin,ethereum' }, { _description: 'Alternative currency', vs_currencies: 'eur', ids: 'bitcoin' } ] } } ``` ## Agent Tests Agent tests validate tool selection (deterministic) and content assertions (partial): ```json { "_description": "Cross-provider analysis", "input": "Compare TVL of Aave on Ethereum vs Arbitrum", "expectedTools": ["defillama/tool/getProtocolTvl"], "expectedContent": ["TVL", "Ethereum", "Arbitrum"] } ``` ### Three-Level Test Model | Level | Field | Deterministic? | Description | |-------|-------|---------------|-------------| | Tool Usage | `expectedTools` | Yes | Which tools the agent must call | | Content | `expectedContent` | Partial | Strings the response should contain | | Quality | Manual review | No | Subjective assessment | ## Test Minimum (TST001) | Primitive | v2 Minimum | v3 Minimum | |-----------|-----------|-----------| | Tool | 1 test | 3 tests | | Resource query | 1 test | 3 tests | | Agent | N/A | 3 tests | ## CLI Commands ```bash # Test a single schema flowmcp test single path/to/schema.mjs # Test all project schemas flowmcp test project ``` --- # "Validation Rules" /docs/specification/validation-rules FlowMCP enforces validation rules when loading schemas, shared lists, groups, resources, and skills. Each rule has a code, severity level, and description. Run validation with `flowmcp validate <schema-path>`. :::note This page covers all validation rules from the formal specification (https://github.com/FlowMCP/flowmcp-spec). Rules are enforced at load-time and during CLI validation. ::: ## Severity Levels | Severity | Description | Effect | |----------|-------------|--------| | `error` | Must fix before use | Schema cannot be loaded | | `warning` | Should fix | Schema loads with warnings | | `info` | Nice to have | Informational only | ## Validation Output ```bash flowmcp validate etherscan/contracts.mjs ``` ```bash # Errors Found VAL014 error main.version: Must match ^3\.\d+\.\d+$ (found "2.0.0") VAL031 error tools: Maximum 8 tools exceeded (found 10) VAL036 warning getContractAbi: output schema is recommended TST001 warning getContractAbi: No tests found 2 errors, 2 warnings Schema cannot be loaded (has errors) # All Valid 0 errors, 0 warnings Schema is valid ``` ## Rules by Category ### Schema Structure Rules (VAL001-VAL005) | Code | Severity | Rule | |------|----------|------| | VAL001 | error | Schema must export `main` as named export | | VAL002 | error | `main` must be an object | | VAL003 | error | `main` must not contain unknown fields | | VAL004 | error | `handlers` (if exported) must be a function | | VAL005 | warning | `handlers` function must return an object with keys matching tool names | ### Main Block — Required Fields (VAL010-VAL016) | Code | Severity | Rule | |------|----------|------| | VAL010 | error | `main.namespace` is required and must be a string | | VAL011 | error | `main.namespace` must match `^[a-z]+$` | | VAL012 | error | `main.name` is required and must be a string | | VAL013 | error | `main.description` is required and must be a string | | VAL014 | error | `main.version` is required and must match `^3\.\d+\.\d+$` | | VAL015 | error | `main.root` is required and must be a valid URL | | VAL016 | error | `main.tools` is required and must be a non-empty object | ### Main Block — Optional Fields (VAL020-VAL026) | Code | Severity | Rule | |------|----------|------| | VAL020 | error | `main.docs` (if present) must be an array of strings | | VAL021 | error | `main.tags` (if present) must be an array of strings | | VAL022 | error | `main.requiredServerParams` (if present) must be an array of strings | | VAL023 | error | `main.headers` (if present) must be a plain object | | VAL024 | error | `main.sharedLists` (if present) must be an array of objects | | VAL025 | error | `main.requiredLibraries` (if present) must be an array of strings | | VAL026 | error | Each entry in `requiredLibraries` must be on the runtime allowlist | ### Tool Rules (VAL030-VAL037) | Code | Severity | Rule | |------|----------|------| | VAL030 | error | Tool name must match `^[a-z][a-zA-Z0-9]*$` | | VAL031 | error | Maximum 8 tools per schema | | VAL032 | error | `tool.method` is required and must be `GET`, `POST`, `PUT`, or `DELETE` | | VAL033 | error | `tool.path` is required and must be a string starting with `/` | | VAL034 | error | `tool.description` is required and must be a string | | VAL035 | error | `tool.parameters` is required and must be an array | | VAL036 | warning | `tool.output` is recommended for new schemas | | VAL037 | info | `tool.async` is a reserved field (not executed in v3.0.0) | ### Parameter Rules (VAL040-VAL050) | Code | Severity | Rule | |------|----------|------| | VAL040 | error | Each parameter must have `position` and `z` objects | | VAL041 | error | `position.key` is required and must be a string | | VAL042 | error | `position.value` is required and must be a string | | VAL043 | error | `position.location` must be `insert`, `query`, or `body` | | VAL044 | error | `z.primitive` is required and must be a valid primitive type | | VAL045 | error | `z.options` must be an array of strings | | VAL046 | error | `enum()` values must not be empty | | VAL047 | error | Shared list interpolation `{{listName:fieldName}}` is only allowed in `enum()` | | VAL048 | error | Referenced shared list must be declared in `main.sharedLists` | | VAL049 | error | Referenced field must exist in the shared list's `meta.fields` | | VAL050 | error | `insert` parameters must have a corresponding `{{key}}` in `tool.path` | ### Output Schema Rules (VAL060-VAL065) | Code | Severity | Rule | |------|----------|------| | VAL060 | error | `output.mimeType` must be a supported MIME type | | VAL061 | error | `output.schema` must be a valid schema definition | | VAL062 | error | `output.schema.type` must match MIME type expectations | | VAL063 | warning | Nested depth should not exceed 4 levels | | VAL064 | error | `properties` is only valid when `type` is `object` | | VAL065 | error | `items` is only valid when `type` is `array` | ### Shared List Reference Rules (VAL070-VAL075) | Code | Severity | Rule | |------|----------|------| | VAL070 | error | `sharedLists[].ref` is required and must be a string | | VAL071 | error | `sharedLists[].version` is required and must be semver | | VAL072 | error | Referenced list must exist in the list registry | | VAL073 | error | Referenced list version must match or be compatible | | VAL074 | error | `filter` (if present) must have valid `key` field | | VAL075 | warning | Unused shared list reference (not used by any parameter or handler) | ### Security Rules (SEC001-SEC005) | Code | Severity | Rule | |------|----------|------| | SEC001 | error | Forbidden pattern found in schema file — no `import` statements allowed | | SEC002 | error | `main` block contains non-serializable value (function, symbol, etc.) | | SEC003 | error | Shared list file contains forbidden pattern | | SEC004 | error | Shared list file contains executable code | | SEC005 | error | `requiredLibraries` contains unapproved package | See Security Model for the complete list of forbidden patterns and error codes. ### Shared List Validation Rules (LST001-LST011) | Code | Severity | Rule | |------|----------|------| | LST001 | error | List must export `list` as named export | | LST002 | error | `list.meta.name` is required and must be unique | | LST003 | error | `list.meta.version` is required and must be semver | | LST004 | error | `list.meta.fields` is required and must be a non-empty array | | LST005 | error | Each field must have `key`, `type`, and `description` | | LST006 | error | `list.entries` is required and must be a non-empty array | | LST007 | error | Each entry must have all required fields | | LST008 | error | Entry field types must match `meta.fields` type declarations | | LST009 | error | `dependsOn` references must resolve to existing lists | | LST010 | error | Circular dependencies are forbidden | | LST011 | error | Maximum dependency depth: 3 levels | ### Group Validation Rules (GRP001-GRP006) | Code | Severity | Rule | |------|----------|------| | GRP001 | error | Group name must match `^[a-z][a-z0-9-]*$` | | GRP002 | error | Maximum 50 tools per group | | GRP003 | error | Tool reference must follow `namespace/file::type::name` format | | GRP004 | error | All referenced tools must be resolvable | | GRP005 | error | Duplicate tool references are forbidden | | GRP006 | error | Group hash must match calculated hash | ### Test Requirements (TST001-TST008) | Code | Severity | Rule | |------|----------|------| | TST001 | error | Each tool must have at least 1 test | | TST002 | error | Each test must have a `_description` field of type string | | TST003 | error | Each test must provide values for all required `{{USER_PARAM}}` parameters | | TST004 | error | Test parameter values must pass the corresponding `z` validation | | TST005 | error | Test objects must be JSON-serializable | | TST006 | error | Test objects must only contain keys matching `{{USER_PARAM}}` parameter keys or `_description` | | TST007 | warning | Tools with enum or chain parameters should test multiple enum values | | TST008 | info | Consider adding tests that demonstrate optional parameter usage | See Tests for the complete test specification. ### Skill Validation Rules (PRM001-PRM008) | Code | Severity | Rule | |------|----------|------| | PRM001 | error | Skill name must match `^[a-z][a-z0-9-]*$` | | PRM002 | error | File must exist at declared path | | PRM003 | error | File must have `# Title` (first line) | | PRM004 | error | File must have `## Workflow` section | | PRM005 | warning | Tool references must resolve in group | | PRM006 | error | Group must have at least one tool | | PRM007 | error | No duplicate skill names within a group | | PRM008 | error | Filename must match skill name | See Groups & Skills for skill format details. ### Resource Validation Rules (RES001-RES023) | Code | Severity | Rule | |------|----------|------| | RES001 | error | `resources` (if present) must be an object | | RES002 | error | Resource name must match `^[a-z][a-zA-Z0-9]*$` | | RES003 | error | Maximum 2 resources per schema | | RES004 | error | `resource.description` is required and must be a string | | RES005 | error | `resource.source` must be `'sqlite'` | | RES006 | error | `resource.database` is required and must be a string ending in `.db` | | RES007 | error | `resource.queries` is required and must be a non-empty object | | RES008 | error | Maximum 4 queries per resource | | RES009 | error | Query name must match `^[a-z][a-zA-Z0-9]*$` | | RES010 | error | `query.description` is required and must be a string | | RES011 | error | `query.sql` is required and must be a string | | RES012 | error | SQL must start with `SELECT` (read-only enforcement) | | RES013 | error | SQL must not contain blocked patterns | | RES014 | error | SQL must use `?` placeholders (no string interpolation) | | RES015 | error | Number of `?` placeholders must match number of parameters | | RES016 | error | `query.parameters` (if present) must be an array | | RES017 | error | Each query parameter must have `key`, `type`, and `description` | | RES018 | error | Query parameter `type` must be `string`, `number`, or `boolean` | | RES019 | error | Query parameter `key` must match `^[a-z][a-zA-Z0-9]*$` | | RES020 | error | No duplicate parameter keys within a query | | RES021 | error | Database file must exist at schema-relative path (runtime check) | | RES022 | warning | Consider adding tests to resource queries | | RES023 | error | SQL must not contain subqueries with write operations | ### Schema-Level Skill Validation Rules (SKL001-SKL025) | Code | Severity | Rule | |------|----------|------| | SKL001 | error | `skills` (if present) must be an array | | SKL002 | error | Maximum 4 skills per schema | | SKL003 | error | Each skill entry must have `name`, `file`, and `description` | | SKL004 | error | Skill `name` must match `^[a-z][a-z0-9-]*$` | | SKL005 | error | Skill `file` must end in `.mjs` | | SKL006 | error | Skill file must exist at schema-relative path | | SKL007 | error | Skill file must export `skill` as named export | | SKL008 | error | `skill.name` must match the `name` in main.skills entry | | SKL009 | error | `skill.version` must be `'flowmcp-skill/1.0.0'` | | SKL010 | error | `skill.description` is required and must be a string | | SKL011 | error | `skill.content` is required and must be a non-empty string | | SKL012 | error | `skill.requires` must be an object with `tools`, `resources`, and `external` arrays | | SKL013 | error | Each `requires.tools` entry must match a tool name in the schema | | SKL014 | error | Each `requires.resources` entry must match a resource name in the schema | | SKL015 | error | `skill.input` (if present) must be an array | | SKL016 | error | Each input must have `key`, `type`, `description`, and `required` fields | | SKL017 | error | Input `key` must match `^[a-z][a-zA-Z0-9]*$` | | SKL018 | error | Input `type` must be `string`, `number`, or `boolean` | | SKL019 | error | No duplicate input keys within a skill | | SKL020 | error | `skill.output` (if present) must be a string | | SKL021 | warning | `{{tool:x}}` placeholders should reference tools in `requires.tools` | | SKL022 | warning | `{{resource:x}}` placeholders should reference resources in `requires.resources` | | SKL023 | warning | `{{input:x}}` placeholders should reference input keys | | SKL024 | error | `{{skill:x}}` placeholders must reference other skills in the schema | | SKL025 | error | No circular skill references via `{{skill:x}}` placeholders | ### Deprecation Rules (DEP001-DEP004) | Code | Severity | Rule | |------|----------|------| | DEP001 | warning | `main.routes` is deprecated — use `main.tools` instead | | DEP002 | info | Legacy `::routeName` format in groups — use `::tool::routeName` discriminator | | DEP003 | warning | `prompts` key in groups.json — renamed to `skills` | | DEP004 | info | `version: '2.x.x'` detected — run `flowmcp migrate` to upgrade | --- # CLI Usage /docs/usage/cli ## Installation ```bash npm install -g github:FlowMCP/flowmcp-cli ``` ## CLI Workflow The CLI follows a three-step pattern: discover tools, activate them, then call them. ## Core Commands | Command | Description | |---------|-------------| | `flowmcp search <query>` | Find tools (max 10 results) | | `flowmcp add <tool-name>` | Activate a tool + show parameters | | `flowmcp call <tool-name> '{json}'` | Call a tool with JSON parameters | | `flowmcp remove <tool-name>` | Deactivate a tool | | `flowmcp list` | Show active tools | | `flowmcp status` | Health check | ## Search, Add, Call 1. **Search for tools** Find tools by keyword. Results include name, description, and the command to add each tool. ```bash flowmcp search ethereum # Shows up to 10 matching tools with name + description flowmcp search "token price" # Refine query if too many results ``` 2. **Add a tool** Activate a tool for your project. The response shows the tool's parameters with types and requirements. ```bash flowmcp add get_contract_abi_etherscan # Response shows: name, description, parameters with types ``` The parameter schema is also saved locally in `.flowmcp/tools/` for reference. 3. **Call a tool** Execute the tool with JSON parameters. Use the parameter information from the `add` response. ```bash flowmcp call get_contract_abi_etherscan '{"address": "0xdAC17F958D2ee523a2206206994597C13D831ec7"}' ``` ## Agent Mode vs Dev Mode The CLI has two operating modes that control which commands are available: | Mode | Commands | Use Case | |------|----------|----------| | **Agent** | search, add, call, remove, list, status | Daily AI agent usage | | **Dev** | + validate, test, migrate | Schema development | ```bash flowmcp mode dev # Switch to dev mode flowmcp mode agent # Switch back to agent mode ``` :::note[Default Mode] Agent mode is the default. It exposes only the commands an AI agent needs to discover, activate, and call tools. Switch to Dev mode for schema development and validation workflows. ::: ## Dev Mode Commands Dev mode unlocks additional commands for schema authors: ```bash flowmcp validate <path> # Validate schema structure flowmcp test single <path> # Live API test flowmcp validate-agent <path> # Validate agent manifest ``` ## Local Project Config When you `add` tools, a `.flowmcp/` directory is created in your project: ``` .flowmcp/ ├── config.json # Active tools + mode └── tools/ # Parameter schemas (auto-generated) └── get_contract_abi_etherscan.json ``` Each file in `tools/` contains the tool name, description, and expected input parameters: ```json { "name": "get_contract_abi_etherscan", "description": "Returns the Contract ABI of a verified smart contract", "parameters": { "address": { "type": "string", "required": true } } } ``` ## API Keys :::tip[API Key Management] Some tools require API keys stored in `~/.flowmcp/.env`. If a `call` fails because of missing keys, add the required key to your global config: ```bash echo "ETHERSCAN_API_KEY=your_key_here" >> ~/.flowmcp/.env ``` Never commit API keys to version control. The `.env` file in `~/.flowmcp/` is your global key store and should stay on your machine only. ::: --- # MCP Server /docs/usage/mcp-server ## What is MCP? The **Model Context Protocol (MCP)** is an open standard for connecting AI models to external tools and data sources. FlowMCP can run as an MCP server, exposing all active schemas as tools to any MCP-compatible AI client. Instead of writing custom server code for each API, you declare schemas and FlowMCP handles the MCP protocol, parameter validation, and API execution. ## Architecture How FlowMCP bridges AI clients and APIs: The AI client sends tool calls over the MCP protocol. FlowMCP resolves the correct schema, validates parameters, calls the upstream API, and returns the result. ## Starting the Server The fastest way to serve schemas is through the CLI: ```bash # Serve all active tools as MCP server (stdio) flowmcp server # Serve with specific schema directory flowmcp server --schemas ./schemas/ # Serve a specific group flowmcp server --group crypto ``` For programmatic control, see the Server Integration guide. ## Client Integration ### Claude Desktop Add FlowMCP to your `claude_desktop_config.json`: ```json { "mcpServers": { "flowmcp": { "command": "npx", "args": ["-y", "flowmcp", "server"], "env": { "ETHERSCAN_API_KEY": "your_key_here", "MORALIS_API_KEY": "your_key_here" } } } } ``` Config file location: - **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json` - **Windows**: `%APPDATA%\Claude\claude_desktop_config.json` :::tip After editing the config, restart Claude Desktop to pick up the new MCP server configuration. ::: ### Cursor Add FlowMCP to your Cursor MCP settings: ```json { "mcpServers": { "flowmcp": { "command": "npx", "args": ["-y", "flowmcp", "server"] } } } ``` Open Cursor Settings and navigate to the MCP section to add this configuration. ### Claude Code Add FlowMCP as a local MCP server: ```bash claude mcp add flowmcp --scope local -- npx -y flowmcp server ``` Claude Code will automatically start the server when needed. ## Environment Variables API keys required by your schemas can be provided in three ways: | Method | Example | Best For | |--------|---------|----------| | `~/.flowmcp/.env` file | `ETHERSCAN_API_KEY=abc123` | Persistent local setup | | `env` block in client config | See Claude Desktop example above | Per-client configuration | | System environment variables | `export ETHERSCAN_API_KEY=abc123` | CI/CD and containers | :::note[Security] API keys are injected as server parameters at runtime and are never exposed to the AI client. They are only used when FlowMCP calls the upstream API on behalf of the AI. ::: ## What Gets Exposed All active tools from your `.flowmcp/config.json` become MCP primitives: | Schema Primitive | MCP Primitive | Description | |-----------------|---------------|-------------| | Tools | MCP Tools | API endpoints the AI can call | | Resources | MCP Resources | Static data the AI can read | | Prompts | MCP Prompts | Pre-built prompt templates | Each tool is registered with its name, description, and a Zod-validated parameter schema -- giving the AI client everything it needs to discover and call the tool correctly. ## What's Next :::note[Server Integration] For programmatic server setup with stdio and HTTP/SSE transports, see the Server Integration guide. ::: :::note[Schema Library] Browse 187+ pre-built schemas ready to serve in the Schema Library. ::: --- # Frequently Asked Questions /introduction/faq ## Do you provide data? No. We create **schemas** that make existing public data easier to reach for AI systems. The data stays with the provider — we store nothing, host nothing, change nothing. We build the adapter between the data source and the AI. ## Why do you need so many data sources? Because real questions in everyday life are never simple. "Should I bike tomorrow?" sounds like a simple question. But to answer it well, the AI needs: - **Weather data** — Will it rain tomorrow? - **Route data** — How far is it? - **Sharing data** — Is there a bike nearby? - **Calendar data** — Do I even have an appointment outside? A single data source delivers a single answer. Only the combination makes the answer truly useful. That is exactly what our schemas make possible. Concrete examples: Use Cases → ## Do I need an API key? For most of our schemas, no. We deliberately start with data sources that are **freely accessible without an API key** — weather data, public transit schedules, geocoding, bike sharing, and more. Schemas that require an API key are clearly marked. ## What is a schema? A schema describes how to query a data source — structured and standardized. It is like an **adapter** between the data provider's API and the AI. One schema per data provider. Multiple tools (individual queries) per schema. The AI does not need to read the API documentation itself — the schema has already done that work. More: Schemas and Tools → ## Can I use your schemas without OpenClaw? Yes. Our schemas work with **any MCP-compatible client** — over 100 applications, including Claude, ChatGPT, Cursor, VS Code Copilot, and many more. OpenClaw is just one of many options. Which client for whom: Clients and Compatibility → ## Is this free? Yes. Everything is **open source under MIT license** — the schemas, the tools, the code, the documentation. Free to use for everyone, without restrictions. ## Do you store my data? No. We have **no backend**. The schemas run locally on your device or through the client of your choice. We see no requests, store no data, have no access to your queries. --- # Use Cases /introduction/use-cases ## How It Works The user enters a sentence in natural language. The agent automatically creates a workflow — with cron jobs, calendar integration, and multiple data sources. No programming, no configuration. --- ## Use Case 1: Dentist Appointment ### The Prompt > Whenever there is a dentist appointment in my calendar: send me a message the day before at 6 PM with the expected weather and which transport option works best. On the morning of the appointment, by 7:30 AM at the latest, give me the exact route. ### What the Agent Does The agent automatically creates a cron job that checks the calendar daily. When it finds a dentist appointment, it schedules two briefings. **Evening before (6:00 PM):** 1. **Calendar** → Dentist appointment tomorrow 10:00 AM, Musterstrasse 5 2. **Weather** → 8°C, light rain from 11 AM 3. **Recommendation via chat:** "Tomorrow light rain from 11 AM. Biking to the appointment is fine (dry), return trip better by public transit." **Morning of the appointment (7:30 AM):** 1. **Weather update** → Rain postponed to noon 2. **Public transit** → S-Bahn running on schedule, no disruptions 3. **Bike sharing** → nextbike at starting location: 3 bikes available 4. **Route** → Bike: 18 min (depart 9:40), Public transit: 25 min (depart 9:30) 5. **Recommendation via chat:** "Bike recommended (18 min, dry). nextbike Alexanderplatz, 3 bikes free. Return: S-Bahn recommended — rain expected from noon." ### Which Data Sources Are Combined? | Data Source | What It Provides | |-------------|-----------------| | **Calendar** (Google/iCal via OpenClaw) | Appointment, time, address | | **Bright Sky** (German Weather Service) | Weather, forecast, rain probability | | **Deutsche Bahn** (transport.rest) | S-Bahn/U-Bahn connections, disruptions | | **VBB** (transport.rest) | Regional public transit Berlin-Brandenburg | | **nextbike** | Bike availability nearby | | **Nominatim** (OpenStreetMap) | Convert address to coordinates | **6 data sources for one answer.** None of them alone could fully answer the question. Only the combination makes the difference — the agent knows that biking there and taking the train back is the best option because it will rain in the afternoon. --- ## Use Case 2: Business Trip ### The Prompt > When there is an appointment with the keyword "business trip" in my calendar: two days before, summarize the train connections to the destination, check the weather there, and show me the nearest stop to the meeting point. On the travel day at 7 AM, give me the current connection with real-time data. ### What the Agent Does **2 days before the trip:** 1. **Calendar** → "Business trip Berlin → Munich", Meeting Leopoldstrasse 10 2. **Train connections** → ICE 8:05 Berlin Hbf (arrival 12:17), ICE 10:05 (arrival 14:13) 3. **Weather Munich** → 15°C, sunny, no rain 4. **Geocoding** → Leopoldstrasse 10 → coordinates → nearest stop: U-Bahn Giselastrasse (300m) 5. **Summary via chat:** "ICE 8:05 recommended (4h12). Munich sunny, 15°C. From station U3 direction Moosach → Giselastrasse, 300m to meeting." **Travel day (7:00 AM):** 1. **Real-time** → ICE 8:05 on schedule, platform 7 2. **Alternative** → If missed: ICE 10:05 3. **Recommendation via chat:** "ICE 8:05 from Berlin Hbf, platform 7, on schedule. Arrival Munich Hbf 12:17. U3 Giselastrasse 300m from meeting. Weather: 15°C, sunny." ### Which Data Sources Are Combined? | Data Source | What It Provides | |-------------|-----------------| | **Calendar** (Google/iCal via OpenClaw) | Appointment, destination, meeting address | | **Deutsche Bahn** (transport.rest) | Long-distance connections, real-time data, platform | | **Bright Sky** (German Weather Service) | Weather at destination | | **Nominatim** (OpenStreetMap) | Meeting address to coordinates, nearest stop | **4 data sources.** The agent plans the entire trip — from train connection through weather to the last mile to the meeting. On the travel day, it updates with real-time data. --- ## What These Examples Show Both use cases share something: 1. **A single prompt** is enough — the agent creates the complete workflow 2. **Multiple data sources** are combined automatically — the user does not need to know where the data comes from 3. **Cron jobs** make it automatic — the agent works in the background, the user gets the result at the right time 4. **Calendar integration** makes it personal — the agent knows when and where **One data source = one answer. Many data sources = a useful answer.** Our schemas make this combination possible — and any developer can build their own use cases with them. ## Beyond Mobility These examples show mobility as one domain — but FlowMCP works wherever data needs to be aggregated and made accessible to AI agents. The same pattern applies to environmental data, public administration, health, finance, or any other domain with structured data sources. Every schema-based integration follows the same approach. - **Environment:** Air quality + weather + pollen count → "Should I go jogging outside today?" - **Government:** Tenders + business registry + federal gazette → "Are there new relevant tenders in my field?" - **Health:** Counseling centers + geocoding + public transit → "Which free counseling is near me and how do I get there?" - **Education:** School holidays + events + weather → "What can I do with the kids during the break?" --- All schemas used: Schema Catalog → Contribute schemas: Community Hub →