Skip to main content
Glama

ask_tom

Get answers in Tom Osborne's voice on marketing, growth, paid ads, eCommerce, content, and leadership by querying his AI agent trained on 21,000 articles and interview corpus.

Instructions

Ask Tom Osborne's second brain a question about marketing, growth, paid ads, eCommerce, content, leadership, or how Tom can help you. Returns Tom's answer in his voice, drawing on his 100-question interview corpus, 15 years of operator memory, and 21,000+ curated marketing knowledge-base articles across 17 verticals (paid, content, SEO, sales, leadership, PR, ads, design, HR, finance, legal, local, marketing fundamentals, business strategy, creator, community, entrepreneurship). Free tier: 5 questions/day per email. Upgrade at tomosborne.me/cmo.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
questionYesWhat you want to ask Tom Osborne's second brain. Examples: 'How do I get to my first 100 customers as a DTC brand?', 'What's Tom's view on bootstrapping a SaaS to first revenue?', 'What worked at AllDomains for the Eclipse SVM launch?'

Implementation Reference

  • The core `askTom` function that makes the API call to POST https://tomosborne.me/api/cmo/chat with the user's question and email, handles errors (network, rate-limit, service-disabled), and appends a footer with model/quota info.
    async function askTom(question: string): Promise<string> {
      const url = `${TOM_API_BASE}/api/cmo/chat`;
      const headers: Record<string, string> = {
        'Content-Type': 'application/json',
        'User-Agent': 'second-brain-mcp/0.1.0',
      };
      if (API_KEY) headers['X-MCP-Key'] = API_KEY;
    
      let res: Response;
      try {
        res = await fetch(url, {
          method: 'POST',
          headers,
          body: JSON.stringify({ email: USER_EMAIL, message: question }),
        });
      } catch (err) {
        throw new Error(
          `Network error contacting tomosborne.me: ${(err as Error).message}. ` +
          `Check your connection or set TOM_API_BASE to a different host.`
        );
      }
    
      let data: ChatResponse;
      try {
        data = (await res.json()) as ChatResponse;
      } catch {
        throw new Error(
          `tomosborne.me returned non-JSON (HTTP ${res.status}). ` +
          `The service may be temporarily unavailable.`
        );
      }
    
      if (res.status === 429 || data.error === 'cap_hit' || data.error === 'rate_limited') {
        throw new Error(
          `Daily question cap reached for ${USER_EMAIL}. ` +
          `Free tier: 5 questions/day. ` +
          `Upgrade by buying Tom a coffee at https://tomosborne.me/cmo (+20 questions, faster replies, voice replies). ` +
          `Or come back tomorrow.`
        );
      }
      if (res.status === 503 || data.error === 'service_disabled') {
        throw new Error(`tomosborne.me chat is temporarily disabled. Try again shortly.`);
      }
      if (!res.ok || !data.ok || !data.reply) {
        throw new Error(
          `tomosborne.me returned an error (HTTP ${res.status}): ${data.error ?? data.message ?? 'unknown'}`
        );
      }
    
      // Append a small footer with model + remaining quota so the calling LLM
      // (and the user) knows how it was answered + how many questions they have left.
      const footer = [
        `\n\n---`,
        `_via Tom's second brain · model: ${data.model_used ?? 'unknown'} · ${data.retrieved_chunks ?? 0} knowledge-base chunks retrieved · ${data.tier_a_remaining ?? '?'} questions left today_`,
      ].join('\n');
      return data.reply + footer;
    }
  • The CallToolRequestSchema handler that dispatches 'ask_tom' calls: parses input with Zod, invokes `askTom`, and returns the reply or an error.
    server.setRequestHandler(CallToolRequestSchema, async (request) => {
      if (request.params.name !== 'ask_tom') {
        throw new Error(`Unknown tool: ${request.params.name}`);
      }
      const parsed = AskTomInput.safeParse(request.params.arguments);
      if (!parsed.success) {
        return {
          content: [{ type: 'text', text: `Invalid input: ${parsed.error.errors.map((e) => e.message).join('; ')}` }],
          isError: true,
        };
      }
      try {
        const reply = await askTom(parsed.data.question);
        return { content: [{ type: 'text', text: reply }] };
      } catch (err) {
        return {
          content: [{ type: 'text', text: (err as Error).message }],
          isError: true,
        };
      }
    });
  • Zod schema `AskTomInput` defining the 'question' string field (min 3, max 2000 chars) with description examples.
    const AskTomInput = z.object({
      question: z
        .string()
        .min(3, 'Question must be at least 3 characters')
        .max(2000, 'Question must be 2000 characters or fewer')
        .describe(
          "What you want to ask Tom Osborne's second brain. Examples: " +
          "'How do I get to my first 100 customers as a DTC brand?', " +
          "'What's Tom's view on bootstrapping a SaaS to first revenue?', " +
          "'What worked at AllDomains for the Eclipse SVM launch?'"
        ),
    });
  • src/server.ts:130-156 (registration)
    ListToolsRequestSchema handler registering the 'ask_tom' tool with its name, description, and input JSON Schema derived from the Zod schema.
    server.setRequestHandler(ListToolsRequestSchema, async () => ({
      tools: [
        {
          name: 'ask_tom',
          description:
            "Ask Tom Osborne's second brain a question about marketing, growth, paid ads, " +
            'eCommerce, content, leadership, or how Tom can help you. ' +
            'Returns Tom\'s answer in his voice, drawing on his 100-question interview corpus, ' +
            '15 years of operator memory, and 21,000+ curated marketing knowledge-base articles ' +
            'across 17 verticals (paid, content, SEO, sales, leadership, PR, ads, design, HR, ' +
            'finance, legal, local, marketing fundamentals, business strategy, creator, community, ' +
            'entrepreneurship). Free tier: 5 questions/day per email. Upgrade at tomosborne.me/cmo.',
          inputSchema: {
            type: 'object',
            properties: {
              question: {
                type: 'string',
                minLength: 3,
                maxLength: 2000,
                description: AskTomInput.shape.question.description,
              },
            },
            required: ['question'],
          },
        },
      ],
    }));
  • TypeScript interface `ChatResponse` defining the shape of the API response from tomosborne.me.
    interface ChatResponse {
      ok: boolean;
      reply?: string;
      error?: string;
      message?: string;
      tier_used?: string;
      model_used?: string;
      retrieved_chunks?: number;
      tier_a_remaining?: number;
    }
Behavior4/5

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

Discloses knowledge sources (100-question interview corpus, 15 years memory, 21k articles across 17 verticals), output format (Tom's voice), and free tier restriction (5 questions/day per email). No annotations provided, but description compensates well.

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

Conciseness4/5

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

Description is informative but slightly verbose. It is well-structured, starting with the purpose and ending with usage limits and upgrade link.

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

Completeness5/5

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

For a single-parameter tool with no output schema or siblings, description fully covers what the tool does, its capabilities, limitations, and even includes upgrade information. No gaps.

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?

Single 'question' parameter with schema coverage 100%. Description adds context about what types of questions are suitable and the knowledge base, but does not add significant parameter semantics beyond schema examples.

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?

Description clearly identifies the action (ask a question), resource (Tom Osborne's second brain), and domain (marketing, growth, etc.). It distinguishes the tool by specifying the knowledge sources.

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

Usage Guidelines4/5

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

Describes appropriate topics and free tier limit, but does not explicitly state when not to use the tool or provide alternatives. With no siblings, this is acceptable but missing some guidance.

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/DegenDoes/second-brain-mcp'

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