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
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:
| Method | Description | Example |
|---|---|---|
client.<tool>(params) | Call a tool directly | await this.mcp('tavily').tavily_search({ query: 'test' }) |
client.list() | List all available tools | await this.mcp('tavily').list() |
client.find(query) | Search tools by name | await this.mcp('tavily').find('search') |
client.call(name, params) | Call tool by name string | await this.mcp('tavily').call('tavily_search', { query: 'test' }) |
Discovery and Inspection
Listing Available Tools
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
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:
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:
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:
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:
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:
| Runtime | MCP Dependencies | Notes |
|---|---|---|
| NCP | ✅ Full support | MCPs configured in NCP profile are available |
| Beam | ❌ Not available | Use @mcp declarations instead |
| CLI | ❌ Not available | Use @mcp declarations instead |
Using with NCP
When running in NCP, configure your MCPs in your NCP profile and they become available via this.mcp():
{
"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 Declaration | this.mcp() Runtime |
|---|---|---|
| Syntax | JSDoc tag | Method call |
| Resolved | Compile time | Runtime |
| Injected as | Class property | Method call |
| Available in | All runtimes | NCP only |
| Use case | Static dependencies | Dynamic orchestration |
@mcp Declaration (compile-time):
/**
* @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):
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.
/**
* 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
- Graceful Degradation: Always handle MCP unavailability
- Timeout Handling: Consider timeouts for slow MCPs
- Error Context: Provide meaningful error messages
- Parallel When Possible: Use
Promise.all()for independent calls - Document Dependencies: List expected MCPs in your photon's JSDoc
