Skip to content

MCP Dependencies: Consuming External MCPs

Photons can consume external MCP servers at runtime, enabling powerful orchestration workflows that combine multiple MCPs written in any language (Python, Rust, Go, Node.js, etc.).

Overview

The this.mcp('name') method returns a proxy client that can:

  • Call tools directly on external MCPs
  • List available tools
  • Search for tools by name

This feature enables polyglot MCP architectures where your photon orchestrates MCPs regardless of their implementation language.

Basic Usage

Calling an External MCP

typescript
export default class Orchestrator {
  /**
   * Search using Tavily MCP
   */
  async search({ query }: { query: string }) {
    const tavily = this.mcp('tavily');
    const results = await tavily.tavily_search({ query, max_results: 5 });
    return results;
  }
}

Available Methods

The MCP client proxy supports three operations:

MethodDescriptionExample
client.<tool>(params)Call a tool directlyawait this.mcp('tavily').tavily_search({ query: 'test' })
client.list()List all available toolsawait this.mcp('tavily').list()
client.find(query)Search tools by nameawait this.mcp('tavily').find('search')
client.call(name, params)Call tool by name stringawait this.mcp('tavily').call('tavily_search', { query: 'test' })

Discovery and Inspection

Listing Available Tools

typescript
async discoverMCPs({ mcpNames }: { mcpNames?: string } = {}) {
  const names = mcpNames?.split(',').map(n => n.trim())
    ?? ['tavily', 'browser', 'sequential-thinking'];

  const statuses = [];
  for (const name of names) {
    try {
      const client = this.mcp(name);
      const tools = await client.list();
      statuses.push({
        name,
        available: true,
        tools: tools.map(t => t.name)
      });
    } catch (error) {
      statuses.push({
        name,
        available: false,
        error: error.message
      });
    }
  }
  return statuses;
}

Searching for Tools

typescript
async findToolsAcrossMCPs({ query }: { query: string }) {
  const mcps = ['tavily', 'browser', 'shell'];
  const results = [];

  for (const name of mcps) {
    try {
      const client = this.mcp(name);
      const tools = await client.find(query);
      if (tools?.length > 0) {
        results.push({ mcp: name, tools });
      }
    } catch {
      // Skip unavailable MCPs
    }
  }
  return results;
}

Orchestration Patterns

Research Workflow

Combine search and browser MCPs to research a topic:

typescript
async researchWorkflow({
  query,
  fetchContent = false
}: {
  query: string;
  fetchContent?: boolean;
}) {
  const workflow = { steps: [], errors: [] };

  // Step 1: Search
  try {
    const tavily = this.mcp('tavily');
    const searchResult = await tavily.tavily_search({ query, max_results: 5 });
    workflow.steps.push({ name: 'Search', mcp: 'tavily', result: searchResult });
  } catch (error) {
    workflow.errors.push(`Search failed: ${error.message}`);
  }

  // Step 2: Optionally fetch content
  if (fetchContent && workflow.steps[0]?.result?.results?.[0]?.url) {
    try {
      const browser = this.mcp('browser');
      const url = workflow.steps[0].result.results[0].url;
      const pageResult = await browser.browser_navigate({ url });
      workflow.steps.push({ name: 'Fetch', mcp: 'browser', result: pageResult });
    } catch (error) {
      workflow.errors.push(`Fetch failed: ${error.message}`);
    }
  }

  return workflow;
}

Parallel Execution

Execute multiple MCP calls concurrently:

typescript
async parallelExecution({ mcpCalls }: { mcpCalls: string }) {
  const calls = JSON.parse(mcpCalls) as Array<{
    mcp: string;
    tool: string;
    params?: any;
  }>;

  const promises = calls.map(async (call) => {
    try {
      const client = this.mcp(call.mcp);
      const result = await client.call(call.tool, call.params || {});
      return { call: `${call.mcp}.${call.tool}`, success: true, result };
    } catch (error) {
      return { call: `${call.mcp}.${call.tool}`, success: false, error: error.message };
    }
  });

  return Promise.all(promises);
}

Chained Workflow

Pipe output from one MCP to another:

typescript
async chainedWorkflow({ steps }: { steps: string }) {
  const stepDefs = JSON.parse(steps) as Array<{
    mcp: string;
    tool: string;
    params?: any;
    outputKey?: string;
    inputFrom?: string;
  }>;

  const context: Record<string, any> = {};

  for (const step of stepDefs) {
    let params = { ...step.params };

    // Inject previous output
    if (step.inputFrom && context[step.inputFrom]) {
      params = { ...params, input: context[step.inputFrom] };
    }

    try {
      const client = this.mcp(step.mcp);
      const result = await client.call(step.tool, params);

      if (step.outputKey) {
        context[step.outputKey] = result;
      }
    } catch (error) {
      throw new Error(`${step.mcp}.${step.tool} failed: ${error.message}`);
    }
  }

  return context;
}

Error Handling

Always handle MCP availability gracefully:

typescript
async checkMCP({ mcpName }: { mcpName: string }) {
  try {
    const client = this.mcp(mcpName);
    const tools = await client.list();
    return {
      name: mcpName,
      available: true,
      tools: tools.map(t => t.name)
    };
  } catch (error) {
    return {
      name: mcpName,
      available: false,
      error: error.message
    };
  }
}

Runtime Requirements

The this.mcp() method requires an MCP client factory to be injected. This is automatically provided when running photons in:

RuntimeMCP DependenciesNotes
NCP✅ Full supportMCPs configured in NCP profile are available
Beam❌ Not availableUse @mcp declarations instead
CLI❌ Not availableUse @mcp declarations instead

Using with NCP

When running in NCP, configure your MCPs in your NCP profile and they become available via this.mcp():

json
{
  "mcps": {
    "tavily": {
      "command": "bunx",
      "args": ["-y", "@anthropic/mcp-server-tavily"]
    },
    "browser": {
      "command": "bunx",
      "args": ["-y", "@anthropic/mcp-server-browser"]
    }
  }
}

Comparison: @mcp vs this.mcp()

Feature@mcp Declarationthis.mcp() Runtime
SyntaxJSDoc tagMethod call
ResolvedCompile timeRuntime
Injected asClass propertyMethod call
Available inAll runtimesNCP only
Use caseStatic dependenciesDynamic orchestration

@mcp Declaration (compile-time):

typescript
/**
 * @mcp github anthropics/mcp-server-github
 */
export default class MyPhoton {
  async useGitHub() {
    // this.github is auto-injected
    return await this.github.list_issues({ repo: 'owner/repo' });
  }
}

this.mcp() Runtime (dynamic):

typescript
export default class Orchestrator {
  async callAnyMCP({ mcpName, toolName, params }: {...}) {
    // Dynamic - can call any configured MCP
    const client = this.mcp(mcpName);
    return await client.call(toolName, params);
  }
}

Complete Example

See the MCP Orchestrator example for a comprehensive demonstration of all these patterns.

typescript
/**
 * MCP Orchestrator Photon
 *
 * Demonstrates how to consume multiple MCPs written in any language
 * as dependencies and orchestrate workflows that combine their capabilities.
 *
 * @version 1.0.0
 * @tags orchestrator, mcp, workflow, integration, polyglot
 * @icon 🔗
 */
export default class MCPOrchestratorPhoton extends PhotonMCP {
  // Discovery, workflows, parallel execution, chaining...
}

Best Practices

  1. Graceful Degradation: Always handle MCP unavailability
  2. Timeout Handling: Consider timeouts for slow MCPs
  3. Error Context: Provide meaningful error messages
  4. Parallel When Possible: Use Promise.all() for independent calls
  5. Document Dependencies: List expected MCPs in your photon's JSDoc

Released under the MIT License.