Skip to content

Commit 979f190

Browse files
committed
update mcp and skills
1 parent c67e8dc commit 979f190

24 files changed

Lines changed: 2317 additions & 995 deletions

File tree

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "workany",
33
"private": true,
4-
"version": "0.1.11",
4+
"version": "0.1.12",
55
"type": "module",
66
"scripts": {
77
"ver": "./scripts/version.sh",

src-api/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "workany-api",
3-
"version": "0.1.11",
3+
"version": "0.1.12",
44
"type": "module",
55
"scripts": {
66
"dev": "node --import tsx --watch src/index.ts",

src-api/src/app/api/agent.ts

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,18 @@ agent.post('/execute', async (c) => {
8282
taskId?: string;
8383
modelConfig?: { apiKey?: string; baseUrl?: string; model?: string };
8484
sandboxConfig?: SandboxConfig;
85-
skillsPath?: string;
85+
skillsConfig?: {
86+
enabled: boolean;
87+
userDirEnabled: boolean;
88+
appDirEnabled: boolean;
89+
skillsPath?: string;
90+
};
91+
mcpConfig?: {
92+
enabled: boolean;
93+
userDirEnabled: boolean;
94+
appDirEnabled: boolean;
95+
mcpConfigPath?: string;
96+
};
8697
}>();
8798

8899
console.log('[AgentAPI] POST /execute received:', {
@@ -94,6 +105,8 @@ agent.post('/execute', async (c) => {
94105
provider: body.sandboxConfig.provider,
95106
}
96107
: null,
108+
skillsConfig: body.skillsConfig,
109+
mcpConfig: body.mcpConfig,
97110
});
98111

99112
if (!body.planId) {
@@ -115,7 +128,8 @@ agent.post('/execute', async (c) => {
115128
body.taskId,
116129
body.modelConfig,
117130
body.sandboxConfig,
118-
body.skillsPath
131+
body.skillsConfig,
132+
body.mcpConfig
119133
)
120134
);
121135

@@ -174,7 +188,8 @@ agent.post('/', async (c) => {
174188
body.modelConfig,
175189
body.sandboxConfig,
176190
body.images,
177-
body.skillsPath
191+
body.skillsConfig,
192+
body.mcpConfig
178193
)
179194
);
180195

src-api/src/core/agent/types.ts

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,34 @@ export interface AgentConfig {
106106
providerConfig?: Record<string, unknown>;
107107
}
108108

109+
/**
110+
* Skills configuration for loading skills from different directories
111+
*/
112+
export interface SkillsConfig {
113+
/** Whether skills are globally enabled */
114+
enabled: boolean;
115+
/** Whether to load skills from user directory (~/.claude/skills) */
116+
userDirEnabled: boolean;
117+
/** Whether to load skills from app directory (workspace/skills) */
118+
appDirEnabled: boolean;
119+
/** Custom skills directory path (legacy support) */
120+
skillsPath?: string;
121+
}
122+
123+
/**
124+
* MCP configuration for loading MCP servers from different config files
125+
*/
126+
export interface McpConfig {
127+
/** Whether MCP is globally enabled */
128+
enabled: boolean;
129+
/** Whether to load MCP servers from user directory (claude config) */
130+
userDirEnabled: boolean;
131+
/** Whether to load MCP servers from app directory (workany config) */
132+
appDirEnabled: boolean;
133+
/** Custom MCP config file path (legacy support) */
134+
mcpConfigPath?: string;
135+
}
136+
109137
export interface AgentOptions {
110138
/** Session ID for continuing conversations */
111139
sessionId?: string;
@@ -125,8 +153,10 @@ export interface AgentOptions {
125153
sandbox?: SandboxConfig;
126154
/** Image attachments for vision capabilities */
127155
images?: ImageAttachment[];
128-
/** Custom skills directory path */
129-
skillsPath?: string;
156+
/** Skills configuration */
157+
skillsConfig?: SkillsConfig;
158+
/** MCP configuration */
159+
mcpConfig?: McpConfig;
130160
}
131161

132162
export interface PlanOptions extends AgentOptions {

src-api/src/extensions/agent/claude/index.ts

Lines changed: 75 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,9 @@ import type {
3737
ConversationMessage,
3838
ExecuteOptions,
3939
ImageAttachment,
40+
McpConfig,
4041
PlanOptions,
42+
SkillsConfig,
4143
} from '@/core/agent/types';
4244
import {
4345
DEFAULT_API_HOST,
@@ -891,6 +893,37 @@ export class ClaudeAgent extends BaseAgent {
891893
});
892894
}
893895

896+
/**
897+
* Build settingSources based on skillsConfig
898+
* Controls which skill directories are loaded by the Claude Agent SDK
899+
*/
900+
private buildSettingSources(skillsConfig?: SkillsConfig): ('user' | 'project')[] {
901+
// If skillsConfig is not provided or skills are globally disabled, use minimal sources
902+
if (!skillsConfig || !skillsConfig.enabled) {
903+
logger.info('[ClaudeAgent] Skills disabled or no config, using project only');
904+
return ['project'];
905+
}
906+
907+
const sources: ('user' | 'project')[] = [];
908+
909+
// 'user' source loads skills from ~/.claude/skills
910+
if (skillsConfig.userDirEnabled) {
911+
sources.push('user');
912+
}
913+
914+
// 'project' source loads skills from project directory
915+
// Always include project for project-specific settings
916+
sources.push('project');
917+
918+
// Note: App directory skills (workspace/skills) are handled separately
919+
// as they are not part of the standard Claude SDK settingSources
920+
if (skillsConfig.appDirEnabled) {
921+
logger.info('[ClaudeAgent] App directory skills enabled (handled via custom skill loading)');
922+
}
923+
924+
return sources.length > 0 ? sources : ['project'];
925+
}
926+
894927
/**
895928
* Build environment variables for the SDK query
896929
* Supports custom API endpoint and API key (including OpenRouter)
@@ -1123,21 +1156,24 @@ User's request (answer this AFTER reading the images):
11231156
if (!claudeCodePath) {
11241157
yield {
11251158
type: 'error',
1126-
message:
1127-
'Claude Code is not installed. Please install it with: npm install -g @anthropic-ai/claude-code',
1159+
message: '__CLAUDE_CODE_NOT_FOUND__',
11281160
};
11291161
yield { type: 'done' };
11301162
return;
11311163
}
11321164

1133-
// Load user-configured MCP servers from ~/.workany/mcp.json
1134-
const userMcpServers = await loadMcpServers();
1165+
// Load user-configured MCP servers based on mcpConfig settings
1166+
const userMcpServers = await loadMcpServers(options?.mcpConfig as McpConfig | undefined);
11351167

11361168
// Build query options
1137-
// Always use ['user', 'project'] to load skills and MCP from user's ~/.claude directory
1169+
// Use settingSources based on skillsConfig to control skill loading
1170+
// - 'user' source loads from ~/.claude directory (User skills)
1171+
// - 'project' source loads from project/.claude directory
11381172
// User's custom API settings from WorkAny settings page are passed via env config
11391173
// which takes priority over ~/.claude/settings.json because we set ANTHROPIC_API_KEY directly
1140-
const settingSources: ('user' | 'project')[] = ['user', 'project'];
1174+
const settingSources: ('user' | 'project')[] = this.buildSettingSources(options?.skillsConfig);
1175+
logger.info(`[Claude ${session.id}] Skills config:`, options?.skillsConfig);
1176+
logger.info(`[Claude ${session.id}] Setting sources: ${settingSources.join(', ')}`);
11411177

11421178
const queryOptions: Options = {
11431179
cwd: sessionCwd,
@@ -1225,13 +1261,31 @@ User's request (answer this AFTER reading the images):
12251261
},
12261262
});
12271263

1228-
// Show simple user-friendly error message
1229-
// Detailed error info is already logged to file
1230-
const logPath = '~/.workany/logs/workany.log';
1231-
yield {
1232-
type: 'error',
1233-
message: `__INTERNAL_ERROR__|${logPath}`,
1234-
};
1264+
// Check for API key related errors
1265+
const errorMessage = error instanceof Error ? error.message : String(error);
1266+
const isApiKeyError =
1267+
errorMessage.includes('Invalid API key') ||
1268+
errorMessage.includes('invalid_api_key') ||
1269+
errorMessage.includes('API key') ||
1270+
errorMessage.includes('authentication') ||
1271+
errorMessage.includes('Please run /login') ||
1272+
errorMessage.includes('Unauthorized') ||
1273+
errorMessage.includes('401');
1274+
1275+
if (isApiKeyError) {
1276+
yield {
1277+
type: 'error',
1278+
message: '__API_KEY_ERROR__',
1279+
};
1280+
} else {
1281+
// Show simple user-friendly error message
1282+
// Detailed error info is already logged to file
1283+
const logPath = '~/.workany/logs/workany.log';
1284+
yield {
1285+
type: 'error',
1286+
message: `__INTERNAL_ERROR__|${logPath}`,
1287+
};
1288+
}
12351289
} finally {
12361290
this.sessions.delete(session.id);
12371291
yield { type: 'done' };
@@ -1274,8 +1328,7 @@ If you need to create any files during planning, use this directory.
12741328
if (!claudeCodePath) {
12751329
yield {
12761330
type: 'error',
1277-
message:
1278-
'Claude Code is not installed. Please install it with: npm install -g @anthropic-ai/claude-code',
1331+
message: '__CLAUDE_CODE_NOT_FOUND__',
12791332
};
12801333
yield { type: 'done' };
12811334
return;
@@ -1427,19 +1480,20 @@ If you need to create any files during planning, use this directory.
14271480
if (!claudeCodePath) {
14281481
yield {
14291482
type: 'error',
1430-
message:
1431-
'Claude Code is not installed. Please install it with: npm install -g @anthropic-ai/claude-code',
1483+
message: '__CLAUDE_CODE_NOT_FOUND__',
14321484
};
14331485
yield { type: 'done' };
14341486
return;
14351487
}
14361488

1437-
// Load user-configured MCP servers from ~/.workany/mcp.json
1438-
const userMcpServers = await loadMcpServers();
1489+
// Load user-configured MCP servers based on mcpConfig settings
1490+
const userMcpServers = await loadMcpServers(options.mcpConfig as McpConfig | undefined);
14391491

14401492
// Build query options
1441-
// Always use ['user', 'project'] to load skills and MCP from user's ~/.claude directory
1442-
const execSettingSources: ('user' | 'project')[] = ['user', 'project'];
1493+
// Use settingSources based on skillsConfig to control skill loading
1494+
const execSettingSources: ('user' | 'project')[] = this.buildSettingSources(options.skillsConfig);
1495+
logger.info(`[Claude ${session.id}] Execute skills config:`, options.skillsConfig);
1496+
logger.info(`[Claude ${session.id}] Execute setting sources: ${execSettingSources.join(', ')}`);
14431497

14441498
const queryOptions: Options = {
14451499
cwd: sessionCwd,

src-api/src/shared/mcp/loader.ts

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -106,20 +106,50 @@ async function loadMcpServersFromFile(
106106
}
107107
}
108108

109+
/**
110+
* MCP configuration interface
111+
*/
112+
export interface McpConfig {
113+
enabled: boolean;
114+
userDirEnabled: boolean;
115+
appDirEnabled: boolean;
116+
mcpConfigPath?: string;
117+
}
118+
109119
/**
110120
* Load MCP servers configuration from multiple sources:
111-
* - ~/.workany/mcp.json (WorkAny specific)
112-
* - ~/.claude/settings.json (Claude Code system config)
121+
* - ~/.workany/mcp.json (WorkAny specific / App directory)
122+
* - ~/.claude/settings.json (Claude Code system config / User directory)
113123
*
124+
* @param mcpConfig Optional config to filter which sources to load
114125
* @returns Record of server name to config, merged from all sources
115126
*/
116-
export async function loadMcpServers(): Promise<
117-
Record<string, McpServerConfig>
118-
> {
127+
export async function loadMcpServers(
128+
mcpConfig?: McpConfig
129+
): Promise<Record<string, McpServerConfig>> {
130+
// If MCP is globally disabled, return empty
131+
if (mcpConfig && !mcpConfig.enabled) {
132+
console.log('[MCP] MCP disabled globally, skipping server load');
133+
return {};
134+
}
135+
119136
const configPaths = getMcpConfigPaths();
120137
const allServers: Record<string, McpServerConfig> = {};
121138

122139
for (const { name, path: configPath } of configPaths) {
140+
// Filter based on mcpConfig settings
141+
if (mcpConfig) {
142+
// 'workany' = App directory, 'claude' = User directory
143+
if (name === 'workany' && mcpConfig.appDirEnabled === false) {
144+
console.log('[MCP] App directory MCP disabled, skipping workany config');
145+
continue;
146+
}
147+
if (name === 'claude' && mcpConfig.userDirEnabled === false) {
148+
console.log('[MCP] User directory MCP disabled, skipping claude config');
149+
continue;
150+
}
151+
}
152+
123153
const servers = await loadMcpServersFromFile(configPath, name);
124154
// Merge servers, workany config takes precedence over claude
125155
Object.assign(allServers, servers);

src-api/src/shared/services/agent.ts

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@ import {
1414
type ConversationMessage,
1515
type IAgent,
1616
type ImageAttachment,
17+
type McpConfig,
1718
type SandboxConfig,
19+
type SkillsConfig,
1820
type TaskPlan,
1921
} from '@/core/agent';
2022
// ============================================================================
@@ -173,7 +175,8 @@ export async function* runExecutionPhase(
173175
taskId?: string,
174176
modelConfig?: { apiKey?: string; baseUrl?: string; model?: string },
175177
sandboxConfig?: SandboxConfig,
176-
skillsPath?: string
178+
skillsConfig?: SkillsConfig,
179+
mcpConfig?: McpConfig
177180
): AsyncGenerator<AgentMessage> {
178181
const agent = getAgent(modelConfig);
179182

@@ -194,6 +197,8 @@ export async function* runExecutionPhase(
194197
sandboxProvider: sandboxConfig?.provider,
195198
apiEndpoint: sandboxConfig?.apiEndpoint,
196199
});
200+
serviceLogger.info('[AgentService] runExecutionPhase skills config:', skillsConfig);
201+
serviceLogger.info('[AgentService] runExecutionPhase mcp config:', mcpConfig);
197202

198203
for await (const message of agent.execute({
199204
planId,
@@ -204,7 +209,8 @@ export async function* runExecutionPhase(
204209
taskId,
205210
abortController: session.abortController,
206211
sandbox: sandboxConfig,
207-
skillsPath,
212+
skillsConfig,
213+
mcpConfig,
208214
})) {
209215
yield message;
210216
}
@@ -222,7 +228,8 @@ export async function* runAgent(
222228
modelConfig?: { apiKey?: string; baseUrl?: string; model?: string },
223229
sandboxConfig?: SandboxConfig,
224230
images?: ImageAttachment[],
225-
skillsPath?: string
231+
skillsConfig?: SkillsConfig,
232+
mcpConfig?: McpConfig
226233
): AsyncGenerator<AgentMessage> {
227234
const agent = getAgent(modelConfig);
228235

@@ -233,6 +240,8 @@ export async function* runAgent(
233240
sandboxProvider: sandboxConfig?.provider,
234241
apiEndpoint: sandboxConfig?.apiEndpoint,
235242
});
243+
serviceLogger.info('[AgentService] runAgent called with skills config:', skillsConfig);
244+
serviceLogger.info('[AgentService] runAgent called with mcp config:', mcpConfig);
236245

237246
for await (const message of agent.run(prompt, {
238247
sessionId: session.id,
@@ -242,7 +251,8 @@ export async function* runAgent(
242251
abortController: session.abortController,
243252
sandbox: sandboxConfig,
244253
images,
245-
skillsPath,
254+
skillsConfig,
255+
mcpConfig,
246256
})) {
247257
yield message;
248258
}
@@ -267,4 +277,6 @@ export type {
267277
AgentConfig,
268278
IAgent,
269279
ImageAttachment,
280+
SkillsConfig,
281+
McpConfig,
270282
};

0 commit comments

Comments
 (0)