# MCP Execution API

The MCP Execution API provides context-aware operations for interacting with MCP servers from your plugin. You can operate as an admin (system-level secrets) or as a specific teammate (user-scoped secrets).

## Quick Start

```typescript
import { definePlugin } from '@majkapp/plugin-kit';

export default definePlugin('my-plugin')
  .withApi(async (ctx) => {
    const { majk } = ctx;

    // Single server operations
    const server = await majk.mcpServers.get('server-id');

    // Admin context - uses system secrets
    const adminTools = await server.asAdmin().discoverTools();
    const result = await server.asAdmin().executeTool('tool-name', { param: 'value' });

    // Teammate context - uses teammate-specific secrets
    const teammateTools = await server.asTeammate('teammate-id').discoverTools();
    const result2 = await server.asTeammate('teammate-id').executeTool('tool-name', {});

    // Everywhere - operate across all servers
    const allTools = await majk.mcpServers.everywhere().asAdmin().discoverTools();
    const anyResult = await majk.mcpServers.everywhere()
      .asTeammate('tm-id')
      .executeTool('any-tool', {});
  });
```

## API Reference

### MCPServerHandle

Extended with context-aware methods:

#### `.asAdmin(): MCPServerScopedHandle`

Creates an admin-scoped handle that uses system-level secrets.

```typescript
const server = await ctx.majk.mcpServers.get('openai-server');
const adminScope = server.asAdmin();

// All operations use system secrets
const tools = await adminScope.discoverTools();
const result = await adminScope.executeTool('gpt-4-call', {
  prompt: 'Hello world'
});
```

#### `.asTeammate(teammateId: string): MCPServerScopedHandle`

Creates a teammate-scoped handle that uses teammate-specific secrets.

```typescript
const server = await ctx.majk.mcpServers.get('openai-server');
const teammateScope = server.asTeammate('teammate-123');

// All operations use teammate's secrets
const tools = await teammateScope.discoverTools();
const result = await teammateScope.executeTool('gpt-4-call', {
  prompt: 'Hello from teammate'
});
```

### MCPServerScopedHandle

Context-aware operations on a single server:

#### `.discoverTools(): Promise<DiscoveredTool[]>`

Discovers available tools from the MCP server.

```typescript
const tools = await server.asAdmin().discoverTools();
console.log(`Found ${tools.length} tools`);

tools.forEach(tool => {
  console.log(`- ${tool.name}: ${tool.description}`);
});
```

**Returns:**
```typescript
interface DiscoveredTool {
  id: string;              // Tool identifier
  name: string;            // Tool name
  description: string;     // Tool description
  inputSchema?: any;       // JSON schema for inputs
  serverId: string;        // Server ID
  serverName: string;      // Server name
}
```

#### `.executeTool(toolName: string, params: Record<string, any>): Promise<any>`

Executes a specific tool on the MCP server.

```typescript
const result = await server.asTeammate('tm-123').executeTool('gpt-4-call', {
  prompt: 'Translate to Spanish: Hello world',
  temperature: 0.7
});

console.log(result.content);
```

#### `.listResources(): Promise<any[]>`

Lists available MCP resources.

```typescript
const resources = await server.asAdmin().listResources();
console.log('Available resources:', resources);
```

#### `.listPrompts(): Promise<any[]>`

Lists available MCP prompts.

```typescript
const prompts = await server.asAdmin().listPrompts();
console.log('Available prompts:', prompts);
```

#### `.getServerInfo(): object`

Gets information about the server and context.

```typescript
const info = server.asTeammate('tm-123').getServerInfo();
console.log(info);
// {
//   id: 'server-id',
//   name: 'OpenAI Server',
//   type: 'local',
//   context: 'teammate'
// }
```

#### `.connect(): Promise<void>`

Manually establishes a connection (lazy by default).

```typescript
await server.asAdmin().connect();
```

#### `.disconnect(): Promise<void>`

Manually closes the connection.

```typescript
await server.asAdmin().disconnect();
```

### MCPServerAPI

Extended with everywhere scope:

#### `.everywhere(): EverywhereScope`

Creates a scope for operating across all MCP servers.

```typescript
const everywhere = ctx.majk.mcpServers.everywhere();

// Then scope to admin or teammate
const adminScope = everywhere.asAdmin();
const teammateScope = everywhere.asTeammate('tm-123');
```

### EverywhereScope

Choose execution context:

#### `.asAdmin(): EverywhereScopedHandle`

Operate as admin across all servers.

```typescript
const scope = ctx.majk.mcpServers.everywhere().asAdmin();
```

#### `.asTeammate(teammateId: string): EverywhereScopedHandle`

Operate as teammate across all servers.

```typescript
const scope = ctx.majk.mcpServers.everywhere().asTeammate('tm-123');
```

### EverywhereScopedHandle

Aggregate operations across all servers:

#### `.discoverTools(): Promise<DiscoveredTool[]>`

Discovers tools from all MCP servers in parallel.

```typescript
const allTools = await ctx.majk.mcpServers
  .everywhere()
  .asAdmin()
  .discoverTools();

console.log(`Found ${allTools.length} tools across all servers`);

// Group by server
const byServer = {};
allTools.forEach(tool => {
  byServer[tool.serverName] = byServer[tool.serverName] || [];
  byServer[tool.serverName].push(tool);
});
```

**Note:** Uses `Promise.allSettled` - some servers can fail without affecting others.

#### `.executeTool(toolName: string, params: Record<string, any>): Promise<any>`

Automatically finds the server that provides a tool and executes it.

```typescript
// No need to know which server has the tool
const result = await ctx.majk.mcpServers
  .everywhere()
  .asTeammate('tm-123')
  .executeTool('any-tool-name', { param: 'value' });
```

**How it works:**
1. Queries the tool repository to find which server provides the tool
2. Creates a connection to that server with the appropriate context
3. Executes the tool
4. Returns the result

#### `.listKnownTools(): Promise<any[]>`

Lists all tools from the tool repository (not live discovery).

```typescript
const knownTools = await ctx.majk.mcpServers
  .everywhere()
  .asAdmin()
  .listKnownTools();
```

#### `.getContextInfo(): object`

Gets information about the execution context.

```typescript
const info = ctx.majk.mcpServers
  .everywhere()
  .asTeammate('tm-123')
  .getContextInfo();

console.log(info);
// {
//   type: 'teammate',
//   id: 'tm-123',
//   metadata: { context: 'teammate', teammateId: 'tm-123', scope: 'user' }
// }
```

## Secret Management

The execution system supports transparent secret resolution using `${secret:name}` syntax in MCP server configurations.

### Creating Servers with Secrets

```typescript
await ctx.majk.mcpServers.create({
  name: 'OpenAI Server',
  type: 'local',
  connectionConfig: {
    command: 'node',
    args: ['mcp-server.js'],
    env: {
      // Each teammate will use their own API key
      OPENAI_API_KEY: '${secret:openai_api_key}',

      // Explicit global scope
      GLOBAL_TOKEN: '${secret:global/api_token}',

      // Environment variable
      PATH: '${env:PATH}'
    }
  }
});
```

### Secret Scopes

- `${secret:name}` - Uses context scope (global for admin, teammate for teammate)
- `${secret:global/name}` - Explicit global scope
- `${secret:teammate/name}` - Explicit teammate scope
- `${secret:project/name}` - Explicit project scope
- `${env:VAR}` - Environment variable

### How It Works

1. When you call `.asAdmin()` or `.asTeammate(id)`, a context is created
2. The context determines which secrets to resolve
3. On connection, `${secret:*}` references are replaced with actual values
4. Each context gets its own connection with its own resolved secrets
5. Admin contexts use system/global secrets
6. Teammate contexts use teammate-specific secrets

## Event Monitoring

All MCP operations emit events that you can subscribe to:

```typescript
// Subscribe to MCP server events
ctx.majk.eventBus.mcpServers().onUpdated(async (server) => {
  // Check for tool discovery events
  if (server.metadata?.type === 'tool_discovery_completed') {
    console.log(`Discovered ${server.metadata.toolCount} tools`);
  }

  // Check for tool execution events
  if (server.metadata?.type === 'tool_execution_completed') {
    console.log(`Executed ${server.metadata.toolName} in ${server.metadata.duration}ms`);
  }
});
```

### Event Types

Discovery events:
- `tool_discovery_started`
- `tool_discovery_completed`
- `tool_discovery_failed`

Execution events:
- `tool_execution_started`
- `tool_execution_completed`
- `tool_execution_failed`

Connection events:
- `connection_established`
- `connection_closed`
- `secrets_resolved`

Everywhere events:
- `everywhere_discovery_started`
- `everywhere_discovery_completed`
- `everywhere_discovery_failed`
- `everywhere_execution_started`
- `everywhere_execution_completed`
- `everywhere_execution_failed`

## Common Patterns

### Tool Discovery Dashboard

```typescript
export default definePlugin('tool-dashboard')
  .withApi(async (ctx) => {
    // Discover tools from all servers
    const allTools = await ctx.majk.mcpServers
      .everywhere()
      .asAdmin()
      .discoverTools();

    // Group by server
    const byServer = {};
    allTools.forEach(tool => {
      if (!byServer[tool.serverName]) {
        byServer[tool.serverName] = [];
      }
      byServer[tool.serverName].push(tool);
    });

    return {
      toolsByServer: byServer,
      totalTools: allTools.length,
      totalServers: Object.keys(byServer).length
    };
  });
```

### Teammate-Specific Tool Execution

```typescript
export default definePlugin('teammate-tools')
  .withApi(async (ctx) => ({
    executeForTeammate: async (teammateId: string, toolName: string, params: any) => {
      try {
        const result = await ctx.majk.mcpServers
          .everywhere()
          .asTeammate(teammateId)
          .executeTool(toolName, params);

        return { success: true, result };
      } catch (error) {
        return { success: false, error: error.message };
      }
    }
  }));
```

### Admin Tool Management

```typescript
export default definePlugin('admin-tools')
  .withApi(async (ctx) => {
    const servers = await ctx.majk.mcpServers.list();

    // Discover tools from each server as admin
    const discoveries = await Promise.all(
      servers.map(async server => {
        try {
          const tools = await server.asAdmin().discoverTools();
          return { serverId: server.id, serverName: server.name, tools };
        } catch (error) {
          return { serverId: server.id, serverName: server.name, error: error.message };
        }
      })
    );

    return { discoveries };
  });
```

### Connection Pool Monitoring

```typescript
// Monitor connection lifecycle
ctx.majk.eventBus.mcpServers().subscribe(async (event) => {
  if (event.metadata?.type === 'connection_established') {
    console.log(`Connection established: ${event.metadata.poolKey}`);
  }

  if (event.metadata?.type === 'connection_closed') {
    console.log(`Connection closed: ${event.metadata.poolKey}`);
  }
});
```

## Best Practices

1. **Use appropriate context**: Admin for system operations, teammate for user actions
2. **Handle errors gracefully**: Tool execution can fail, always wrap in try/catch
3. **Monitor events**: Subscribe to events for observability
4. **Leverage everywhere**: Use `.everywhere()` when you don't know which server has a tool
5. **Clean up connections**: Connections auto-cleanup after 5 min idle, but you can manually disconnect
6. **Secure secrets**: Never log or expose resolved secrets

## TypeScript Support

Full TypeScript support with type inference:

```typescript
import type {
  MCPServerHandle,
  MCPServerScopedHandle,
  DiscoveredTool,
  EverywhereScope,
  EverywhereScopedHandle
} from '@majkapp/plugin-kit';

// Type-safe operations
const server: MCPServerHandle = await ctx.majk.mcpServers.get('id');
const scope: MCPServerScopedHandle = server.asAdmin();
const tools: DiscoveredTool[] = await scope.discoverTools();
```

## Performance Notes

- **Connection pooling**: Connections are reused within the same context
- **Parallel discovery**: `everywhere().discoverTools()` runs in parallel across servers
- **Lazy connections**: Connections are created on first use, not on `.asAdmin()/.asTeammate()`
- **Auto cleanup**: Idle connections are cleaned up after 5 minutes

## Migration from Legacy API

If you were using direct MCP operations:

```typescript
// OLD
const tools = await ctx.majk.mcpServers.get('id').then(s => s.getTools());

// NEW - with context awareness
const tools = await ctx.majk.mcpServers.get('id').then(s => s.asAdmin().discoverTools());
```
