> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ctxprotocol.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Tool Metadata

> Configure _meta on your MCP tools to declare rate-limit hints, context injection requirements, pricing, and mode eligibility

Every MCP tool definition supports a `_meta` field for platform-level configuration that sits alongside `inputSchema` and `outputSchema`. This guide covers the three `_meta` capabilities:

* **[Rate-Limit Hints](#rate-limit-hints)**: tell the runtime how to pace calls to your API
* **[Context Injection](#context-injection)**: declare what user-specific data the platform should inject into your tool's arguments
* **[Pricing & Execute Mode Eligibility](#pricing-&-execute-mode-eligibility)**: control where your methods appear and set per-call execute pricing

<Info>
  Optional contributor-side search helpers are **not** a new `_meta` contract in this rollout. If your venue needs the LLM-backed pattern described in [Optional Contributor Search Helpers](/guides/optional-contributor-search-helpers), keep that logic and any helper-private state inside the contributor. Standardized helper evidence now rides in contributor `searchMetadata` payloads and developer traces, not in ad hoc `_meta.searchHelper` fields.
</Info>

***

## Rate-Limit Hints

<Info>
  Use this section if your MCP server wraps APIs with strict quotas (hobby/free tiers, burst limits, or expensive fan-out endpoints).
</Info>

### Why This Exists

In the marketplace, each contributor has different upstream constraints. Without pacing hints, an agent can generate loops that are valid code but operationally unsafe for your API key tier.

Publishing `_meta.rateLimit` (or `_meta.rateLimitHints`) gives Context two things:

* Better execution choices (prefer batch/snapshot tools, avoid risky fan-out patterns)
* Safer runtime pacing (respect cooldowns and concurrency intent)

This reduces avoidable `429`/timeout loops and improves first-pass completion.

### Metadata Shape

Add these fields to each tool definition under `_meta.rateLimit`:

| Field                   | Type      | Purpose                                          |
| ----------------------- | --------- | ------------------------------------------------ |
| `maxRequestsPerMinute`  | number    | Throughput budget for this tool                  |
| `cooldownMs`            | number    | Minimum delay between sequential calls           |
| `maxConcurrency`        | number    | Parallel call ceiling for this tool              |
| `supportsBulk`          | boolean   | Whether the tool already handles batch retrieval |
| `recommendedBatchTools` | string\[] | Preferred alternatives to fan-out loops          |
| `notes`                 | string    | Human guidance for execution behavior            |

### TypeScript Example

```typescript theme={null}
const TOOLS = [
  {
    name: "get_portfolio_snapshot",
    description: "Fetch portfolio snapshot for a wallet",
    _meta: {
      rateLimit: {
        maxRequestsPerMinute: 30,
        cooldownMs: 2000,
        maxConcurrency: 1,
        supportsBulk: true,
        recommendedBatchTools: ["get_portfolio_snapshot"],
        notes: "Hobby tier: prefer snapshot over per-asset fan-out loops.",
      },
    },
    inputSchema: { type: "object", properties: {} },
    outputSchema: { type: "object", properties: {} },
  },
];
```

### Python Example

```python theme={null}
TOOLS = [
    {
        "name": "get_portfolio_snapshot",
        "description": "Fetch portfolio snapshot for a wallet",
        "_meta": {
            "rateLimit": {
                "maxRequestsPerMinute": 30,
                "cooldownMs": 2000,
                "maxConcurrency": 1,
                "supportsBulk": True,
                "recommendedBatchTools": ["get_portfolio_snapshot"],
                "notes": "Hobby tier: prefer snapshot over per-asset fan-out loops.",
            }
        },
        "inputSchema": {"type": "object", "properties": {}},
        "outputSchema": {"type": "object", "properties": {}},
    }
]
```

### Contributor Guidance

* Publish hints per tool, not only globally. Heavy fan-out endpoints and lightweight snapshot endpoints should have different guidance.
* Keep `maxConcurrency` conservative for expensive or quota-sensitive endpoints.
* Populate `recommendedBatchTools` whenever you offer a better aggregation endpoint.
* Treat `notes` as execution guidance for real-world constraints (for example, "call alone", "use shallow scan first").

<Warning>
  These hints are not a substitute for server-side protections. Keep your own upstream safeguards (timeouts, retries, and pacing) in the MCP server.
</Warning>

### Rate-Limit Reference Implementations

* TypeScript: [Normalized Data Provider](https://github.com/ctxprotocol/sdk/tree/main/examples/server/normalized-data-provider), Execute-mode data broker with background ingestion, `inputSchema` `default`/`examples`, and zero upstream rate limits
* TypeScript: [Polymarket contributor server](https://github.com/ctxprotocol/sdk/tree/main/examples/server/polymarket-contributor), large-surface Query-mode tool with 20+ methods and complex schemas
* Python: [Hummingbot contributor server](https://github.com/ctxprotocol/sdk-python/tree/main/examples/server/hummingbot-contributor)

***

## Context Injection

The **Context Injection** pattern enables MCP tools to receive user-specific data without handling authentication or credentials:

* **Portfolio Data:** Hyperliquid positions, balances, P\&L
* **Prediction Markets:** Polymarket positions and orders
* **Wallet Data:** EVM addresses and token balances

<Info>
  **Why "Context Injection"?** The platform fetches user data client-side and injects it directly into your tool's arguments. Your server never sees private keys or credentials, just structured data ready to analyze.
</Info>

### When to Use Context Injection

| Use Case           | Context Type    | Example Query                            |
| ------------------ | --------------- | ---------------------------------------- |
| Portfolio analysis | `"hyperliquid"` | "Analyze my Hyperliquid positions"       |
| Position tracking  | `"polymarket"`  | "How are my Polymarket bets performing?" |
| Wallet insights    | `"wallet"`      | "What tokens do I hold on Base?"         |

<Tip>
  **User-specific vs. Public Data**: Use Context Injection when your tool needs to answer "my" questions. For public data (e.g., "What's the ETH price?"), you don't need context injection, just fetch it directly.
</Tip>

### Quick Start (TypeScript)

#### 1. Declare Context Requirements

Add `_meta.contextRequirements` to your tool definition:

```typescript theme={null}
const TOOLS = [
  {
    name: "analyze_my_positions",
    description: "Analyze user's Hyperliquid perpetual positions",
    inputSchema: {
      type: "object",
      properties: {
        portfolio: {
          type: "object",
          description: "User's Hyperliquid portfolio (injected by platform)",
        },
      },
      required: ["portfolio"],
    },
    outputSchema: {
      type: "object",
      properties: {
        totalPnl: { type: "number" },
        topPosition: { type: "string" },
        riskScore: { type: "number" },
      },
    },
    _meta: {
      contextRequirements: ["hyperliquid"],
    },
  },
];
```

#### 2. Handle the Injected Data

The platform injects the data as the `portfolio` argument:

```typescript theme={null}
server.setRequestHandler(CallToolRequestSchema, async (request) => {
  if (request.params.name === "analyze_my_positions") {
    const { portfolio } = request.params.arguments;

    const positions = portfolio.assetPositions || [];
    const accountValue = portfolio.marginSummary?.accountValue || 0;

    const totalPnl = positions.reduce(
      (sum, p) => sum + (p.position?.unrealizedPnl || 0),
      0
    );

    const topPosition = positions.sort(
      (a, b) => Math.abs(b.position?.szi || 0) - Math.abs(a.position?.szi || 0)
    )[0];

    return {
      content: [{ type: "text", text: `Total PnL: \$${totalPnl.toFixed(2)}` }],
      structuredContent: {
        totalPnl,
        topPosition: topPosition?.position?.coin || "None",
        riskScore: calculateRiskScore(positions),
      },
    };
  }
});
```

### Quick Start (Python)

#### 1. Declare Context Requirements

```python theme={null}
from mcp.server import Server
from mcp.types import Tool

tools = [
    Tool(
        name="analyze_my_positions",
        description="Analyze user's Hyperliquid perpetual positions",
        inputSchema={
            "type": "object",
            "properties": {
                "portfolio": {
                    "type": "object",
                    "description": "User's Hyperliquid portfolio (injected by platform)",
                },
            },
            "required": ["portfolio"],
        },
        _meta={
            "contextRequirements": ["hyperliquid"],
        },
    ),
]
```

#### 2. Handle the Injected Data

```python theme={null}
@server.call_tool()
async def handle_tool(name: str, arguments: dict):
    if name == "analyze_my_positions":
        portfolio = arguments.get("portfolio", {})

        positions = portfolio.get("assetPositions", [])
        account_value = portfolio.get("marginSummary", {}).get("accountValue", 0)

        total_pnl = sum(
            p.get("position", {}).get("unrealizedPnl", 0)
            for p in positions
        )

        return {
            "content": [{"type": "text", "text": f"Total PnL: ${total_pnl:.2f}"}],
            "structuredContent": {
                "totalPnl": total_pnl,
                "accountValue": float(account_value),
                "positionCount": len(positions),
            },
        }
```

### How It Works

```
┌─────────────────────────────────────────────────────────────────────┐
│  1. User: "Analyze my Hyperliquid positions"                        │
└─────────────────────────────────────────────────────────────────────┘
                              ↓
┌─────────────────────────────────────────────────────────────────────┐
│  2. Context App checks tool's _meta.contextRequirements             │
└─────────────────────────────────────────────────────────────────────┘
                              ↓
┌─────────────────────────────────────────────────────────────────────┐
│  3. App fetches user's Hyperliquid data (client-side, public API)   │
└─────────────────────────────────────────────────────────────────────┘
                              ↓
┌─────────────────────────────────────────────────────────────────────┐
│  4. App injects data as `portfolio` argument to your tool           │
└─────────────────────────────────────────────────────────────────────┘
                              ↓
┌─────────────────────────────────────────────────────────────────────┐
│  5. Your tool receives structured data, returns analysis            │
└─────────────────────────────────────────────────────────────────────┘
```

### Supported Context Types

#### `"hyperliquid"`: Perpetuals & Spot

Hyperliquid portfolio data for perpetual and spot positions.

```typescript theme={null}
{
  marginSummary: {
    accountValue: string;
    totalMarginUsed: string;
    totalNtlPos: string;
    totalRawUsd: string;
  };
  assetPositions: Array<{
    position: {
      coin: string;
      szi: string;
      entryPx: string;
      positionValue: string;
      unrealizedPnl: string;
      leverage: {
        type: string;
        value: number;
      };
    };
  }>;
  crossMarginSummary: { ... };
}
```

<Info>
  **Data Source**: Fetched from the public Hyperliquid Info API using the user's linked wallet address.
</Info>

#### `"polymarket"`: Prediction Markets

Polymarket positions, orders, and market data.

```typescript theme={null}
{
  positions: Array<{
    market: string;
    outcome: string;
    size: number;
    avgPrice: number;
    currentPrice: number;
    pnl: number;
  }>;
  openOrders: Array<{
    market: string;
    side: "buy" | "sell";
    price: number;
    size: number;
  }>;
  balance: number;
}
```

#### `"wallet"`: Generic EVM Wallet

Basic wallet data for any EVM-compatible chain.

```typescript theme={null}
{
  address: string;
  chainId: number;
  balances: Array<{
    token: string;
    address: string;
    balance: string;
    decimals: number;
    usdValue?: number;
  }>;
  nativeBalance: string;
}
```

### Multiple Context Types

Your tool can request multiple context types:

```typescript theme={null}
{
  name: "cross_platform_analysis",
  _meta: {
    contextRequirements: ["hyperliquid", "wallet"]
  },
  inputSchema: {
    type: "object",
    properties: {
      hyperliquidPortfolio: { type: "object" },
      walletPortfolio: { type: "object" },
    },
  },
}
```

<Warning>
  **Naming Convention**: When requesting multiple contexts, the platform injects each with a descriptive key (e.g., `hyperliquidPortfolio`, `walletPortfolio`). Check your tool's argument names match what you declare in `inputSchema`.
</Warning>

### Context Injection Best Practices

<AccordionGroup>
  <Accordion title="Validate the injected data" icon="shield">
    The user might not have linked the required account, or they might have empty positions:

    ```typescript theme={null}
    if (!portfolio || !portfolio.assetPositions) {
      return {
        content: [{ type: "text", text: "No Hyperliquid data available. Please link your wallet in Settings." }],
        structuredContent: { error: "NO_DATA", message: "Portfolio not linked" },
      };
    }
    ```
  </Accordion>

  <Accordion title="Handle empty portfolios gracefully" icon="inbox">
    A linked wallet with no positions is valid, don't treat it as an error:

    ```typescript theme={null}
    if (positions.length === 0) {
      return {
        content: [{ type: "text", text: "No open positions found." }],
        structuredContent: { positions: [], summary: "No active positions" },
      };
    }
    ```
  </Accordion>

  <Accordion title="Document the portfolio argument" icon="book">
    Make it clear in your tool description that portfolio data is required:

    ```typescript theme={null}
    description: "Analyze user's Hyperliquid positions. Requires linked Hyperliquid wallet."
    ```
  </Accordion>

  <Accordion title="Use outputSchema for structured responses" icon="code">
    Your analysis results should be machine-readable:

    ```typescript theme={null}
    outputSchema: {
      type: "object",
      properties: {
        totalPnl: { type: "number", description: "Total unrealized P&L in USD" },
        riskLevel: { type: "string", enum: ["low", "medium", "high"] },
        recommendations: { type: "array", items: { type: "string" } },
      },
    }
    ```
  </Accordion>
</AccordionGroup>

### Security Model

<Info>
  **Zero Credential Exposure**: Your MCP server never sees private keys, API secrets, or wallet signatures. The platform fetches public data client-side and passes only the structured result to your tool.
</Info>

| Security Property          | How It's Achieved                                                                                  |
| -------------------------- | -------------------------------------------------------------------------------------------------- |
| No private keys exposed    | Platform fetches data client-side                                                                  |
| No API keys needed         | Uses public APIs (Hyperliquid Info, etc.)                                                          |
| User controls data sharing | User explicitly links accounts in Settings                                                         |
| Read-only by default       | Context injection is read-only; use [Handshakes](/guides/handshake-architecture) for write actions |

### Future Context Types

<Note>
  **Beyond Crypto**: The Context Injection system is designed to support any type of user data. We're starting with high-value crypto data, but the architecture supports identity, preferences, and external service data.
</Note>

Planned context types (not yet available):

| Context Type | Description              |
| ------------ | ------------------------ |
| `"dydx"`     | dYdX perpetual positions |
| `"aave"`     | Aave lending positions   |
| `"uniswap"`  | Uniswap LP positions     |
| `"ens"`      | ENS names and records    |

<Tip>
  **Want a specific context type?** [Open an issue](https://github.com/ctxprotocol/sdk/issues) describing your use case and the data you need.
</Tip>

### Complete Example: Portfolio Analyzer

```typescript theme={null}
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { createContextMiddleware } from "@ctxprotocol/sdk";
import express from "express";

const app = express();
app.use(express.json());
app.use("/mcp", createContextMiddleware());

const TOOLS = [
  {
    name: "analyze_portfolio_risk",
    description: "Analyze risk metrics for user's Hyperliquid portfolio",
    inputSchema: {
      type: "object",
      properties: {
        portfolio: { type: "object" },
        riskTolerance: {
          type: "string",
          enum: ["conservative", "moderate", "aggressive"],
          default: "moderate",
        },
      },
      required: ["portfolio"],
    },
    outputSchema: {
      type: "object",
      properties: {
        riskScore: { type: "number", minimum: 0, maximum: 100 },
        leverageRatio: { type: "number" },
        largestPosition: { type: "string" },
        recommendations: { type: "array", items: { type: "string" } },
      },
    },
    _meta: {
      contextRequirements: ["hyperliquid"],
    },
  },
];

const server = new Server(
  { name: "portfolio-analyzer", version: "1.0.0" },
  { capabilities: { tools: {} } }
);

server.setRequestHandler(CallToolRequestSchema, async (request) => {
  if (request.params.name === "analyze_portfolio_risk") {
    const { portfolio, riskTolerance = "moderate" } = request.params.arguments;

    if (!portfolio?.assetPositions) {
      return {
        content: [{ type: "text", text: "Please link your Hyperliquid wallet in Settings." }],
        structuredContent: { error: "NO_PORTFOLIO" },
      };
    }

    const positions = portfolio.assetPositions;
    const accountValue = parseFloat(portfolio.marginSummary?.accountValue || "0");
    
    const totalNotional = positions.reduce(
      (sum, p) => sum + Math.abs(parseFloat(p.position?.positionValue || "0")),
      0
    );
    const leverageRatio = accountValue > 0 ? totalNotional / accountValue : 0;
    
    const largestPosition = positions.sort(
      (a, b) => 
        Math.abs(parseFloat(b.position?.positionValue || "0")) -
        Math.abs(parseFloat(a.position?.positionValue || "0"))
    )[0];

    let riskScore = 0;
    if (leverageRatio > 5) riskScore += 40;
    else if (leverageRatio > 2) riskScore += 20;
    
    if (positions.length > 10) riskScore += 20;
    if (positions.some(p => parseFloat(p.position?.leverage?.value || 0) > 10)) riskScore += 30;

    const recommendations = [];
    if (leverageRatio > 3) recommendations.push("Consider reducing leverage");
    if (positions.length > 8) recommendations.push("Portfolio may be over-diversified");

    return {
      content: [{
        type: "text",
        text: `Risk Score: ${riskScore}/100. Leverage: ${leverageRatio.toFixed(2)}x`,
      }],
      structuredContent: {
        riskScore,
        leverageRatio,
        largestPosition: largestPosition?.position?.coin || "None",
        recommendations,
      },
    };
  }
});

app.listen(3000);
```

***

## Pricing & Execute Mode Eligibility

<Info>
  **Most contributors start here:** If you just want your tool available in the Context app (Query mode), you don't need to configure anything in this section, set a listing response price when you register and you're done. This section is for contributors who also want SDK developers to call their methods directly with per-call pricing (Execute mode).
</Info>

### How Query and Execute Work

Context runs **one marketplace with two modes**:

| Mode        | Who is the librarian?                                                     | Billing                     | What you get                                                                                             |
| ----------- | ------------------------------------------------------------------------- | --------------------------- | -------------------------------------------------------------------------------------------------------- |
| **Query**   | Context runtime (used via the app **or** `client.query.run()` in the SDK) | Pay-per-response (\~\$0.10) | Managed librarian output: plain answer or structured evidence package assembled from up to 100 MCP calls |
| **Execute** | Your app/agent (used via `client.tools.execute()` in the SDK)             | Per method call (\~\$0.001) | Raw structured data with session spending limit visibility                                               |

<Info>
  SDK consumers can use **both** modes. Use `client.query.run()` when you want Context to be the librarian (`answer_with_evidence` or `evidence_only`, pay-per-response). Use `client.tools.execute()` when your agent is the librarian (raw data, per-call pricing, spending limits). See [TypeScript SDK](/sdk/reference) or [Python SDK](/sdk/python-reference).
</Info>

Execute pricing is typically **\~1/100 of listing response price** because a single Query response can invoke up to 100 method calls internally. When developers pay per call, they need proportionally lower prices.

### Metadata Shape

Add these fields to each method definition under `_meta`:

| Field                | Type                                                 | Purpose                                                                                                                                               |
| -------------------- | ---------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| `surface`            | `"answer"` \| `"execute"` \| `"both"`                | Which mode(s) this method is eligible for (API field name)                                                                                            |
| `queryEligible`      | `boolean`                                            | Whether the Query runtime can select this method for answer synthesis                                                                                 |
| `latencyClass`       | `"instant"` \| `"fast"` \| `"slow"` \| `"streaming"` | Discovery/eligibility hint (Query excludes `streaming` by default). Does **not** change the per-call timeout — every method gets the same 60s budget. |
| `pricing.executeUsd` | `string`                                             | Per-call execute price in USD (required for Execute eligibility)                                                                                      |
| `pricing.queryUsd`   | `string`                                             | Reserved metadata for future per-method query billing (not used for billing in current rollout)                                                       |

### Execute Eligibility Rule

<Warning>
  **Hard gate:** A method is Execute-eligible **only** when it has an explicit `_meta.pricing.executeUsd` value. There is no fallback to listing-level pricing for Execute calls.

  * Methods **with** `_meta.pricing.executeUsd` → visible in both Query and Execute (depending on `surface` field)
  * Methods **without** `_meta.pricing.executeUsd` → **Query-only:** invisible to SDK Execute discovery
</Warning>

### Default Execute Price (Contributor Shortcut)

When you list your tool on the marketplace, you can set a single **default execute price** in the contribute form. This convenience input fans out to every method's `_meta.pricing.executeUsd` at ingestion time. You don't need to edit each method individually unless you want per-method overrides.

| Approach                 | How it works                                                                             |
| ------------------------ | ---------------------------------------------------------------------------------------- |
| **Simple (recommended)** | Set one default execute price in the contribute form → applied to all methods            |
| **Advanced**             | Set `_meta.pricing.executeUsd` per method in your MCP server code → preserved on refresh |
| **Mixed**                | Default fills gaps; explicit method-level values take precedence                         |

### TypeScript Example

```typescript theme={null}
const TOOLS = [
  {
    name: "get_gas_prices",
    description: "Get current gas prices for EVM chains",
    _meta: {
      surface: "both",
      queryEligible: true,
      latencyClass: "instant",
      pricing: {
        executeUsd: "0.001",
      },
    },
    inputSchema: {
      type: "object",
      properties: {
        chainId: { type: "number", description: "EVM chain ID" },
      },
    },
    outputSchema: {
      type: "object",
      properties: {
        gasPrice: { type: "number" },
        unit: { type: "string" },
      },
      required: ["gasPrice", "unit"],
    },
  },
  {
    name: "stream_live_trades",
    description: "Stream real-time trade data (Execute only)",
    _meta: {
      surface: "execute",
      queryEligible: false,
      latencyClass: "streaming",
      pricing: {
        executeUsd: "0.0005",
      },
    },
    inputSchema: { type: "object", properties: {} },
    outputSchema: { type: "object", properties: {} },
  },
];
```

### Python Example

```python theme={null}
TOOLS = [
    {
        "name": "get_gas_prices",
        "description": "Get current gas prices for EVM chains",
        "_meta": {
            "surface": "both",
            "queryEligible": True,
            "latencyClass": "instant",
            "pricing": {
                "executeUsd": "0.001",
            },
        },
        "inputSchema": {
            "type": "object",
            "properties": {
                "chainId": {"type": "number", "description": "EVM chain ID"},
            },
        },
        "outputSchema": {
            "type": "object",
            "properties": {
                "gasPrice": {"type": "number"},
                "unit": {"type": "string"},
            },
            "required": ["gasPrice", "unit"],
        },
    },
]
```

### Choosing a Mode

| Your tool is best for...                                                                                                                | Set `surface` to        | Set `queryEligible`                                 | Set `pricing.executeUsd`                                                 |
| --------------------------------------------------------------------------------------------------------------------------------------- | ----------------------- | --------------------------------------------------- | ------------------------------------------------------------------------ |
| Curated intelligence (e.g., "smart money analysis")                                                                                     | `"answer"` or `"both"`  | `true`                                              | Optional (set if you also want Execute revenue)                          |
| Normalized raw data (e.g., "cross-exchange prices")                                                                                     | `"execute"` or `"both"` | `false` for execute-only, `true` for both           | **Required**                                                             |
| Direct functional primitive the agent calls and consumes raw (e.g., "web page → markdown", "structured extraction", "docs/code search") | `"execute"` or `"both"` | `false` for execute-only, `true` if also answerable | **Required**                                                             |
| Write or authed action (e.g., "post to X", "update a CRM record")                                                                       | `"execute"`             | `false`                                             | **Required** (also wire up [Handshakes](/guides/handshake-architecture)) |
| Both curated answers and raw data access                                                                                                | `"both"`                | `true`                                              | **Required**                                                             |

<Tip>
  **Execute is not only normalized data.** Two kinds of method belong on the Execute surface. The first is normalized raw data (prices, candles, funding). The second is a **functional primitive**: any tool whose buyer is an agent that wants to call a function and use the result directly, such as fetch-and-clean a page, extract structured records, or search docs. For methods that perform a write or authed action, set `surface: "execute"` and implement the [Handshake Architecture](/guides/handshake-architecture) for the signature, transaction, or OAuth step.
</Tip>

<Tip>
  **Mixed listings are supported.** A single tool can have some methods for Query only (curated intelligence tools), some for Execute only (streaming, raw data, or functional/action primitives), and some for both. The platform routes each method based on its individual metadata.
</Tip>

<Warning>
  **Visibility is bidirectional.** The platform gates visibility in both directions:

  * **No execute price → invisible in Execute.** Methods without `_meta.pricing.executeUsd` will not appear in SDK Execute discovery (`client.tools.execute()`). They remain Query-only.
  * **Execute-only → invisible in Query.** Methods with `surface: "execute"` and `queryEligible: false` will not appear in the Context app or `client.query.run()`. The runtime will never select them for answer synthesis. They are SDK-only.

  Design your `surface` and `queryEligible` settings intentionally, they control where your tool shows up.
</Warning>

### Choosing Eligibility With The Code Interpreter

Query mode includes a managed `code_interpreter` tool that runs **Python** in an isolated [Vercel Sandbox](https://vercel.com/docs/sandbox) microVM with `pandas`, `numpy`, `scipy`, `matplotlib`, `statsmodels`, and `pyarrow` available (see [Code Interpreter](/architecture/code-interpreter) for the full data flow contract). After the runtime calls your MCP methods, the interpreter can turn raw structured outputs into derived metrics, structured chart artifacts, matplotlib PNG visualisations, markdown-ready tables in the synthesized answer, rolling statistics, correlations, drawdowns, and other post-processed evidence.

That changes the default choice for high-quality read methods. In the pre-interpreter model, a method that returned raw time-series data might have looked useful only for SDK Execute callers. In the current Query runtime, that same method can be first-class answer evidence because Context can compute the derived analysis after retrieval.

The interpreter operates **only** on data your method already returned. It does not make HTTP, network, or marketplace calls of its own — `networkPolicy: "deny-all"` is enforced at the platform level — so methods that return portable structured data are first-class answer fuel, while methods that withhold the underlying data and only return a polished narrative leave the interpreter unable to compute on top.

Use this rule of thumb:

| Method output is...                                                                                                                                          | Recommended `surface`  | Recommended `queryEligible` | Why                                                                                              |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------- | --------------------------- | ------------------------------------------------------------------------------------------------ |
| Time-series prices, OHLCV candles, funding history, open interest history, orderbook snapshots, large trade lists, or other high-fidelity public market data | `"both"`               | `true`                      | Query can retrieve the data, then use `code_interpreter` to compute and render derived analysis. |
| Curated intelligence or already-aggregated analysis                                                                                                          | `"answer"` or `"both"` | `true`                      | Query can use the method directly as grounded answer evidence.                                   |
| Write actions, signatures, transaction submission, internal handshake callbacks, or approval flows                                                           | `"execute"`            | `false`                     | Query answers must not perform side effects without an explicit app-controlled workflow.         |
| Streaming feeds or long-lived subscriptions                                                                                                                  | `"execute"`            | `false`                     | Query excludes `latencyClass: "streaming"` methods from answer synthesis.                        |
| Raw outputs that are only meaningful inside your own external execution context                                                                              | `"execute"`            | `false`                     | Query cannot safely interpret data that lacks portable structure or semantics.                   |

<Tip>
  If a read method can produce portable structured data that a Python analyst (with pandas + numpy) could compute over, prefer `surface: "both"` and `queryEligible: true`. The method does not need to return a polished narrative; the managed librarian and the Python `code_interpreter` can derive the final answer.
</Tip>

### Pricing Guidance

| Listing response price (Query) | Suggested execute price per method | Ratio |
| ------------------------------ | ---------------------------------- | ----- |
| \$0.01                         | \$0.0001                           | 1/100 |
| \$0.05                         | \$0.0005                           | 1/100 |
| \$0.10                         | \$0.001                            | 1/100 |
| \$0.25                         | \$0.0025                           | 1/100 |

<Note>
  The \~1/100 ratio is guidance, not a protocol-enforced rule. A Query response can make up to 100 method calls in one turn, so execute pricing per call should be proportionally lower.
</Note>

### Legacy Tools (No `_meta`)

Tools listed before this metadata rollout continue working in Query with unchanged economics. They receive default metadata at ingestion time:

| Field                | Default                             |
| -------------------- | ----------------------------------- |
| `surface`            | `"both"`                            |
| `queryEligible`      | `true`                              |
| `latencyClass`       | `"slow"`                            |
| `pricing.executeUsd` | Not set → **Query-only in Execute** |

<Info>
  To enable Execute visibility for a legacy tool, either set a default execute price in the Developer Tools page or add `_meta.pricing.executeUsd` to your MCP server code and click "Refresh Skills".
</Info>

### Reference Implementation

For a production example of `_meta` pricing and rate-limit metadata, see the [Normalized Data Provider](https://github.com/ctxprotocol/sdk/tree/main/examples/server/normalized-data-provider). For a large-surface contributor with 20+ methods and complex schemas, see the [Polymarket contributor server](https://github.com/ctxprotocol/sdk/tree/main/examples/server/polymarket-contributor).

The following `_meta` capabilities are recommended for production tools with upstream API constraints:

* Per-method `_meta.pricing.executeUsd` with a default fallback and explicit opt-out for query-only methods
* Per-method `_meta.rateLimit` hints built from upstream API tier constraints
* Batch tool recommendations via `recommendedBatchTools`

***

## Related Guides

<CardGroup cols={2}>
  <Card title="Handshake Architecture" icon="handshake" href="/guides/handshake-architecture">
    For write actions: signatures, transactions, OAuth
  </Card>

  <Card title="Build & List Your Tool" icon="hammer" href="/guides/build-tools">
    Complete guide to building MCP tools
  </Card>
</CardGroup>
