Skip to main content
Glama

create_price_watch

Register a webhook to monitor AI model price changes. Specify model, price field, and trigger for threshold breaches or any change. Get HMAC-signed POST alerts to your callback URL.

Instructions

Register a webhook watch on a model price change. Costs 1 credit. Watch lives 90 days. Each fire is an HMAC-signed POST to callback_url.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
modelYesModel name (e.g. "Claude Opus 4.7")
fieldYesWhich price field to watch
opYesTrigger: lt = below threshold, gt = above, changes = any change
thresholdNoRequired when op is lt or gt; ignored for changes
callback_urlYesHTTPS URL to POST to when the watch fires
secretNoOptional shared secret used to HMAC-sign delivery bodies

Implementation Reference

  • The async handler function that executes the create_price_watch tool logic: builds the request body with spec (type: 'price', model, field, op, and optional threshold) and callback_url, optionally includes secret, then calls fetchJSON('/premium/watches', {method: 'POST', body, auth: true}) and returns the created watch ID and credits remaining.
    async ({ model, field, op, threshold, callback_url, secret }) => {
      const body: Record<string, unknown> = {
        spec: { type: 'price', model, field, op, ...(typeof threshold === 'number' ? { threshold } : {}) },
        callback_url,
      };
      if (secret !== undefined) body.secret = secret;
      const data = (await fetchJSON('/premium/watches', { method: 'POST', body, auth: true })) as {
        watch: { id: string; expires_at: string };
        billing?: { credits_remaining?: number };
      };
      return {
        content: [
          {
            type: 'text' as const,
            text: `Created watch ${data.watch.id} (expires ${data.watch.expires_at}). Credits remaining: ${data.billing?.credits_remaining ?? '?'}`,
          },
        ],
      };
    },
  • Zod schema for create_price_watch inputs: model (string), field (enum: inputPrice/outputPrice/blended), op (enum: lt/gt/changes), threshold (optional number), callback_url (string), secret (optional string).
    {
      model: z.string().describe('Model name (e.g. "Claude Opus 4.7")'),
      field: z.enum(['inputPrice', 'outputPrice', 'blended']).describe('Which price field to watch'),
      op: z.enum(['lt', 'gt', 'changes']).describe('Trigger: lt = below threshold, gt = above, changes = any change'),
      threshold: z.number().optional().describe('Required when op is lt or gt; ignored for changes'),
      callback_url: z.string().describe('HTTPS URL to POST to when the watch fires'),
      secret: z.string().optional().describe('Optional shared secret used to HMAC-sign delivery bodies'),
    },
  • Registration of the 'create_price_watch' tool with server.tool(), including its description and the full handler.
    server.tool(
      'create_price_watch',
      'Register a webhook watch on a model price change. Costs 1 credit. Watch lives 90 days. Each fire is an HMAC-signed POST to callback_url.',
      {
        model: z.string().describe('Model name (e.g. "Claude Opus 4.7")'),
        field: z.enum(['inputPrice', 'outputPrice', 'blended']).describe('Which price field to watch'),
        op: z.enum(['lt', 'gt', 'changes']).describe('Trigger: lt = below threshold, gt = above, changes = any change'),
        threshold: z.number().optional().describe('Required when op is lt or gt; ignored for changes'),
        callback_url: z.string().describe('HTTPS URL to POST to when the watch fires'),
        secret: z.string().optional().describe('Optional shared secret used to HMAC-sign delivery bodies'),
      },
      async ({ model, field, op, threshold, callback_url, secret }) => {
        const body: Record<string, unknown> = {
          spec: { type: 'price', model, field, op, ...(typeof threshold === 'number' ? { threshold } : {}) },
          callback_url,
        };
        if (secret !== undefined) body.secret = secret;
        const data = (await fetchJSON('/premium/watches', { method: 'POST', body, auth: true })) as {
          watch: { id: string; expires_at: string };
          billing?: { credits_remaining?: number };
        };
        return {
          content: [
            {
              type: 'text' as const,
              text: `Created watch ${data.watch.id} (expires ${data.watch.expires_at}). Credits remaining: ${data.billing?.credits_remaining ?? '?'}`,
            },
          ],
        };
      },
    );
  • The fetchJSON helper function used by the handler to make authenticated HTTP requests to the TensorFeed API. It handles GET/POST/DELETE, attaches Bearer token when auth:true, and throws on 402/401/other errors.
    async function fetchJSON(path: string, opts: FetchOptions = {}): Promise<unknown> {
      const headers: Record<string, string> = {
        'User-Agent': `TensorFeed-MCP/${SDK_VERSION}`,
      };
      if (opts.body !== undefined) headers['Content-Type'] = 'application/json';
      if (opts.auth) {
        const token = process.env.TENSORFEED_TOKEN;
        if (!token) {
          throw new Error(
            'TENSORFEED_TOKEN env var is not set. Premium MCP tools require a bearer token. ' +
              'Buy credits at https://tensorfeed.ai/developers/agent-payments and pass the returned tf_live_... token via the TENSORFEED_TOKEN env var in your MCP client config.',
          );
        }
        headers['Authorization'] = `Bearer ${token}`;
      }
      const res = await fetch(`${API_BASE}${path}`, {
        method: opts.method ?? 'GET',
        headers,
        ...(opts.body !== undefined ? { body: JSON.stringify(opts.body) } : {}),
      });
      if (!res.ok) {
        let errPayload: unknown;
        try {
          errPayload = await res.json();
        } catch {
          errPayload = await res.text().catch(() => '');
        }
        if (res.status === 402) {
          throw new Error(
            `Payment required (402). Your token may be out of credits. Top up at https://tensorfeed.ai/developers/agent-payments. Detail: ${JSON.stringify(errPayload)}`,
          );
        }
        if (res.status === 401) {
          throw new Error(
            `Token rejected (401). Check that TENSORFEED_TOKEN is set to a valid tf_live_... token. Detail: ${JSON.stringify(errPayload)}`,
          );
        }
        throw new Error(`API error ${res.status}: ${JSON.stringify(errPayload)}`);
      }
      return res.json();
    }
Behavior4/5

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

With no annotations, the description carries the full burden. It discloses key behaviors: costs 1 credit, watch lives 90 days, and each fire results in an HMAC-signed POST to the callback URL. This provides valuable context beyond the schema.

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

Conciseness5/5

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

The description is three sentences, concise and front-loaded with the core purpose. Every sentence adds value: purpose, cost/duration, and delivery details. No wasted words.

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

Completeness3/5

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

The description covers cost, expiration, and delivery mechanism, but it does not explain the return value format, error handling, or what happens on successful creation. For a tool with 6 parameters and no output schema, more context on outcomes would be beneficial.

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

Parameters3/5

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

The input schema has 100% description coverage and includes enums for two parameters. The description does not add additional semantic meaning to the parameters beyond what the schema already provides, so baseline 3 is appropriate.

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

Purpose5/5

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

The description clearly states the action: 'Register a webhook watch on a model price change.' It uses a specific verb and resource, and the name 'create_price_watch' combined with the description distinguishes it from sibling tools like 'create_digest_watch' and 'create_status_watch'.

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

Usage Guidelines3/5

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

The description does not explicitly state when to use this tool versus alternatives. While the name and resource type imply it is for price watches, there is no guidance on when not to use it or which sibling to choose for other scenarios.

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

Install Server

Other Tools

Latest Blog Posts

MCP directory API

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

curl -X GET 'https://glama.ai/api/mcp/v1/servers/RipperMercs/tensorfeed'

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