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.