@operor/skills 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,465 @@
1
+ {
2
+ "version": 1,
3
+ "updatedAt": "2026-02-18T00:00:00.000Z",
4
+ "skills": [
5
+ {
6
+ "name": "shopify",
7
+ "displayName": "Shopify",
8
+ "description": "Manage Shopify store products, customers, and orders via GraphQL Admin API",
9
+ "category": "commerce",
10
+ "tags": ["ecommerce", "shopify", "orders", "products", "inventory", "customers"],
11
+ "transport": "stdio",
12
+ "package": "shopify-mcp",
13
+ "command": "npx",
14
+ "args": ["-y", "shopify-mcp"],
15
+ "envVars": {
16
+ "SHOPIFY_ACCESS_TOKEN": {
17
+ "description": "Custom app access token from Shopify Admin > Settings > Apps",
18
+ "required": true,
19
+ "placeholder": "shpat_..."
20
+ },
21
+ "MYSHOPIFY_DOMAIN": {
22
+ "description": "Your store domain, e.g. my-store.myshopify.com",
23
+ "required": true,
24
+ "placeholder": "my-store.myshopify.com"
25
+ }
26
+ },
27
+ "tools": [
28
+ { "name": "get-products", "description": "List or search products in the store" },
29
+ { "name": "get-product-by-id", "description": "Get a specific product by ID" },
30
+ { "name": "createProduct", "description": "Create a new product" },
31
+ { "name": "get-customers", "description": "List or search customers" },
32
+ { "name": "update-customer", "description": "Update customer details" },
33
+ { "name": "get-customer-orders", "description": "Get orders for a specific customer" },
34
+ { "name": "get-orders", "description": "List or search orders" },
35
+ { "name": "get-order-by-id", "description": "Get details of a specific order" },
36
+ { "name": "update-order", "description": "Update an existing order" }
37
+ ],
38
+ "maturity": "community",
39
+ "vendor": "GeLi2001",
40
+ "docsUrl": "https://github.com/GeLi2001/shopify-mcp"
41
+ },
42
+ {
43
+ "name": "stripe",
44
+ "displayName": "Stripe",
45
+ "description": "Payment processing, subscriptions, invoicing, and financial operations via Stripe API",
46
+ "category": "payments",
47
+ "tags": ["payments", "stripe", "billing", "subscriptions", "invoices", "refunds"],
48
+ "transport": "stdio",
49
+ "package": "@stripe/mcp",
50
+ "command": "npx",
51
+ "args": ["-y", "@stripe/mcp", "--tools=all", "--api-key=${STRIPE_SECRET_KEY}"],
52
+ "envVars": {
53
+ "STRIPE_SECRET_KEY": {
54
+ "description": "Stripe secret API key (use restricted key for production)",
55
+ "required": true,
56
+ "placeholder": "sk_live_..."
57
+ }
58
+ },
59
+ "tools": [
60
+ { "name": "create_payment_intent", "description": "Create a payment intent" },
61
+ { "name": "list_customers", "description": "List Stripe customers" },
62
+ { "name": "create_refund", "description": "Refund a charge or payment intent" },
63
+ { "name": "list_subscriptions", "description": "List active subscriptions" }
64
+ ],
65
+ "maturity": "official",
66
+ "vendor": "Stripe",
67
+ "docsUrl": "https://github.com/stripe/agent-toolkit"
68
+ },
69
+ {
70
+ "name": "hubspot",
71
+ "displayName": "HubSpot CRM",
72
+ "description": "Manage contacts, companies, deals, pipelines, and associations in HubSpot CRM",
73
+ "category": "crm",
74
+ "tags": ["crm", "hubspot", "contacts", "deals", "sales", "pipeline"],
75
+ "transport": "stdio",
76
+ "package": "@cloud9-labs/mcp-hubspot",
77
+ "command": "npx",
78
+ "args": ["-y", "@cloud9-labs/mcp-hubspot"],
79
+ "envVars": {
80
+ "HUBSPOT_ACCESS_TOKEN": {
81
+ "description": "HubSpot Private App access token with CRM scopes",
82
+ "required": true,
83
+ "placeholder": "pat-..."
84
+ }
85
+ },
86
+ "tools": [
87
+ { "name": "hubspot_create_contact", "description": "Create a new contact" },
88
+ { "name": "hubspot_get_contact", "description": "Get contact details" },
89
+ { "name": "hubspot_update_contact", "description": "Update a contact" },
90
+ { "name": "hubspot_search_contacts", "description": "Search contacts by criteria" },
91
+ { "name": "hubspot_list_contacts", "description": "List all contacts" },
92
+ { "name": "hubspot_create_company", "description": "Create a new company" },
93
+ { "name": "hubspot_get_company", "description": "Get company details" },
94
+ { "name": "hubspot_search_companies", "description": "Search companies" },
95
+ { "name": "hubspot_create_deal", "description": "Create a new deal" },
96
+ { "name": "hubspot_get_deal", "description": "Get deal details" },
97
+ { "name": "hubspot_update_deal", "description": "Update a deal" },
98
+ { "name": "hubspot_list_deals", "description": "List all deals" },
99
+ { "name": "hubspot_get_pipelines", "description": "Get sales pipelines" },
100
+ { "name": "hubspot_create_association", "description": "Associate records together" }
101
+ ],
102
+ "maturity": "community",
103
+ "vendor": "Cloud9 Labs",
104
+ "docsUrl": "https://github.com/cloud9-labs/mcp-hubspot"
105
+ },
106
+ {
107
+ "name": "salesforce",
108
+ "displayName": "Salesforce",
109
+ "description": "Full Salesforce CRM integration with SOQL queries, CRUD operations, and backup/recovery",
110
+ "category": "crm",
111
+ "tags": ["crm", "salesforce", "leads", "opportunities", "sales"],
112
+ "transport": "stdio",
113
+ "package": "@aiondadotcom/mcp-salesforce",
114
+ "command": "npx",
115
+ "args": ["-y", "@aiondadotcom/mcp-salesforce"],
116
+ "envVars": {},
117
+ "tools": [
118
+ { "name": "salesforce_auth", "description": "Authenticate with Salesforce" },
119
+ { "name": "salesforce_setup", "description": "Interactive credential setup" },
120
+ { "name": "salesforce_query", "description": "Run SOQL queries" },
121
+ { "name": "salesforce_create", "description": "Create records" },
122
+ { "name": "salesforce_update", "description": "Update records" },
123
+ { "name": "salesforce_delete", "description": "Delete records" },
124
+ { "name": "salesforce_describe", "description": "Describe object schema" },
125
+ { "name": "salesforce_backup", "description": "Backup data" },
126
+ { "name": "salesforce_time_machine", "description": "Restore from backup" }
127
+ ],
128
+ "maturity": "community",
129
+ "vendor": "AiondaDotCom",
130
+ "docsUrl": "https://github.com/AiondaDotCom/mcp-salesforce"
131
+ },
132
+ {
133
+ "name": "zendesk",
134
+ "displayName": "Zendesk",
135
+ "description": "Search Help Center articles, retrieve article content, and create support tickets",
136
+ "category": "support",
137
+ "tags": ["support", "zendesk", "helpdesk", "tickets", "knowledge-base"],
138
+ "transport": "stdio",
139
+ "command": "node",
140
+ "args": ["dist/index.js"],
141
+ "envVars": {
142
+ "ZENDESK_SUBDOMAIN": {
143
+ "description": "Your Zendesk subdomain (e.g. 'mycompany' from mycompany.zendesk.com)",
144
+ "required": true,
145
+ "placeholder": "mycompany"
146
+ },
147
+ "ZENDESK_USERNAME": {
148
+ "description": "Admin email address for Zendesk",
149
+ "required": true,
150
+ "placeholder": "admin@example.com"
151
+ },
152
+ "ZENDESK_API_TOKEN": {
153
+ "description": "API token from Zendesk Admin > Channels > API",
154
+ "required": true,
155
+ "placeholder": "..."
156
+ }
157
+ },
158
+ "tools": [
159
+ { "name": "search_articles", "description": "Search Help Center articles" },
160
+ { "name": "get_article_content", "description": "Get full article content" },
161
+ { "name": "create_ticket", "description": "Create a support ticket" }
162
+ ],
163
+ "maturity": "community",
164
+ "vendor": "hatappo",
165
+ "docsUrl": "https://github.com/hatappo/mcp-server-zendesk"
166
+ },
167
+ {
168
+ "name": "intercom",
169
+ "displayName": "Intercom",
170
+ "description": "Search and filter Intercom conversations by date, status, and source type",
171
+ "category": "support",
172
+ "tags": ["support", "intercom", "conversations", "messaging", "customer-support"],
173
+ "transport": "stdio",
174
+ "command": "node",
175
+ "args": ["dist/index.js"],
176
+ "envVars": {
177
+ "INTERCOM_API_KEY": {
178
+ "description": "Intercom API key from Developer Hub",
179
+ "required": true,
180
+ "placeholder": "..."
181
+ }
182
+ },
183
+ "tools": [
184
+ { "name": "search-conversations", "description": "Search and filter conversations" }
185
+ ],
186
+ "maturity": "community",
187
+ "vendor": "fabian1710",
188
+ "docsUrl": "https://github.com/fabian1710/mcp-intercom"
189
+ },
190
+ {
191
+ "name": "mailchimp",
192
+ "displayName": "Mailchimp",
193
+ "description": "Full Mailchimp Marketing API: campaigns, audiences, templates, segments, automations, and reports",
194
+ "category": "marketing",
195
+ "tags": ["marketing", "email", "mailchimp", "campaigns", "newsletters", "audiences"],
196
+ "transport": "stdio",
197
+ "command": "node",
198
+ "args": ["dist/index.js"],
199
+ "envVars": {
200
+ "MAILCHIMP_API_KEY": {
201
+ "description": "Mailchimp API key in format xxx-usXX (datacenter auto-extracted)",
202
+ "required": true,
203
+ "placeholder": "abc123-us21"
204
+ }
205
+ },
206
+ "tools": [
207
+ { "name": "get_lists", "description": "Get all audiences/lists" },
208
+ { "name": "get_members", "description": "Get audience members" },
209
+ { "name": "add_member", "description": "Add a subscriber" },
210
+ { "name": "search_members", "description": "Search subscribers" },
211
+ { "name": "get_campaigns", "description": "Get campaigns" },
212
+ { "name": "create_campaign", "description": "Create a campaign" },
213
+ { "name": "send_campaign", "description": "Send a campaign" },
214
+ { "name": "get_templates", "description": "Get email templates" },
215
+ { "name": "get_campaign_report", "description": "Get campaign analytics" },
216
+ { "name": "get_segments", "description": "Get audience segments" },
217
+ { "name": "get_automations", "description": "Get automations" }
218
+ ],
219
+ "maturity": "community",
220
+ "vendor": "Snouzy",
221
+ "docsUrl": "https://github.com/Snouzy/m6b9-mcp-mailchimp"
222
+ },
223
+ {
224
+ "name": "sendgrid",
225
+ "displayName": "SendGrid",
226
+ "description": "Transactional and marketing email: send emails, manage contacts/lists, templates, and domain authentication",
227
+ "category": "marketing",
228
+ "tags": ["marketing", "email", "sendgrid", "transactional", "campaigns"],
229
+ "transport": "stdio",
230
+ "package": "primrose-mcp",
231
+ "command": "npx",
232
+ "args": ["-y", "primrose-mcp", "sendgrid"],
233
+ "envVars": {
234
+ "SENDGRID_API_KEY": {
235
+ "description": "SendGrid API key from Settings > API Keys",
236
+ "required": true,
237
+ "placeholder": "SG...."
238
+ }
239
+ },
240
+ "tools": [
241
+ { "name": "send_email", "description": "Send a transactional email" },
242
+ { "name": "send_template_email", "description": "Send email using a template" },
243
+ { "name": "send_bulk_email", "description": "Send bulk emails" },
244
+ { "name": "list_contacts", "description": "List marketing contacts" },
245
+ { "name": "create_contact", "description": "Create a contact" },
246
+ { "name": "search_contacts", "description": "Search contacts" },
247
+ { "name": "list_templates", "description": "List email templates" },
248
+ { "name": "get_global_stats", "description": "Get email delivery stats" }
249
+ ],
250
+ "maturity": "community",
251
+ "vendor": "Primrose",
252
+ "docsUrl": "https://github.com/primrose-mcp/primrose-mcp-sendgrid"
253
+ },
254
+ {
255
+ "name": "slack",
256
+ "displayName": "Slack",
257
+ "description": "Read/search Slack messages, list channels/users, manage user groups, and post messages",
258
+ "category": "communication",
259
+ "tags": ["communication", "slack", "messaging", "chat", "team"],
260
+ "transport": "stdio",
261
+ "command": "npx",
262
+ "args": ["-y", "@anthropic/mcp-slack"],
263
+ "envVars": {
264
+ "SLACK_BOT_TOKEN": {
265
+ "description": "Slack Bot token (xoxb-...) with appropriate scopes",
266
+ "required": true,
267
+ "placeholder": "xoxb-..."
268
+ }
269
+ },
270
+ "tools": [
271
+ { "name": "conversations_history", "description": "Get channel message history" },
272
+ { "name": "conversations_replies", "description": "Get thread replies" },
273
+ { "name": "conversations_search_messages", "description": "Search messages" },
274
+ { "name": "channels_list", "description": "List channels" },
275
+ { "name": "users_search", "description": "Search users" },
276
+ { "name": "conversations_add_message", "description": "Post a message" }
277
+ ],
278
+ "maturity": "community",
279
+ "vendor": "Anthropic",
280
+ "docsUrl": "https://github.com/modelcontextprotocol/servers/tree/main/src/slack"
281
+ },
282
+ {
283
+ "name": "linear",
284
+ "displayName": "Linear",
285
+ "description": "Manage Linear issues, projects, and teams — create, update, search, and track work items",
286
+ "category": "productivity",
287
+ "tags": ["productivity", "linear", "issues", "project-management", "tracking"],
288
+ "transport": "stdio",
289
+ "package": "@tacticlaunch/mcp-linear",
290
+ "command": "npx",
291
+ "args": ["-y", "@tacticlaunch/mcp-linear"],
292
+ "envVars": {
293
+ "LINEAR_API_TOKEN": {
294
+ "description": "Linear personal API key from Settings > API",
295
+ "required": true,
296
+ "placeholder": "lin_api_..."
297
+ }
298
+ },
299
+ "tools": [
300
+ { "name": "linear_get_issues", "description": "List issues" },
301
+ { "name": "linear_create_issue", "description": "Create an issue" },
302
+ { "name": "linear_update_issue", "description": "Update an issue" },
303
+ { "name": "linear_search_issues", "description": "Search issues" },
304
+ { "name": "linear_get_projects", "description": "List projects" },
305
+ { "name": "linear_create_project", "description": "Create a project" },
306
+ { "name": "linear_get_teams", "description": "List teams" },
307
+ { "name": "linear_add_comment", "description": "Add a comment to an issue" }
308
+ ],
309
+ "maturity": "community",
310
+ "vendor": "Tactic Launch",
311
+ "docsUrl": "https://github.com/tacticlaunch/mcp-linear"
312
+ },
313
+ {
314
+ "name": "notion",
315
+ "displayName": "Notion",
316
+ "description": "Access and manage Notion pages, databases, and comments for documentation and knowledge management",
317
+ "category": "productivity",
318
+ "tags": ["productivity", "notion", "documentation", "wiki", "databases"],
319
+ "transport": "stdio",
320
+ "package": "mcp-notion-server",
321
+ "command": "npx",
322
+ "args": ["-y", "mcp-notion-server"],
323
+ "envVars": {
324
+ "NOTION_API_KEY": {
325
+ "description": "Notion integration token from notion.so/my-integrations",
326
+ "required": true,
327
+ "placeholder": "ntn_..."
328
+ }
329
+ },
330
+ "tools": [
331
+ { "name": "notion_search", "description": "Search pages and databases" },
332
+ { "name": "notion_get_page", "description": "Get page content" },
333
+ { "name": "notion_create_page", "description": "Create a new page" },
334
+ { "name": "notion_update_page", "description": "Update a page" },
335
+ { "name": "notion_get_database", "description": "Get database schema" },
336
+ { "name": "notion_query_database", "description": "Query a database" },
337
+ { "name": "notion_create_database", "description": "Create a database" },
338
+ { "name": "notion_get_comments", "description": "Get page comments" },
339
+ { "name": "notion_add_comment", "description": "Add a comment" }
340
+ ],
341
+ "maturity": "community",
342
+ "vendor": "suekou",
343
+ "docsUrl": "https://github.com/suekou/mcp-notion-server"
344
+ },
345
+ {
346
+ "name": "brave-search",
347
+ "displayName": "Brave Search",
348
+ "description": "Web, local, video, image, and news search with AI-powered summaries via Brave Search API",
349
+ "category": "search",
350
+ "tags": ["search", "web", "brave", "internet", "news"],
351
+ "transport": "stdio",
352
+ "package": "@brave/brave-search-mcp-server",
353
+ "command": "npx",
354
+ "args": ["-y", "@brave/brave-search-mcp-server"],
355
+ "envVars": {
356
+ "BRAVE_API_KEY": {
357
+ "description": "Brave Search API key from brave.com/search/api",
358
+ "required": true,
359
+ "placeholder": "BSA..."
360
+ }
361
+ },
362
+ "tools": [
363
+ { "name": "brave_web_search", "description": "Search the web" },
364
+ { "name": "brave_local_search", "description": "Search for local businesses and places" },
365
+ { "name": "brave_video_search", "description": "Search for videos" },
366
+ { "name": "brave_image_search", "description": "Search for images" },
367
+ { "name": "brave_news_search", "description": "Search news articles" },
368
+ { "name": "brave_summarizer", "description": "Get AI-powered search summaries" }
369
+ ],
370
+ "maturity": "official",
371
+ "vendor": "Brave",
372
+ "docsUrl": "https://github.com/brave/brave-search-mcp-server"
373
+ },
374
+ {
375
+ "name": "google-sheets",
376
+ "displayName": "Google Sheets",
377
+ "description": "Create and modify Google Sheets spreadsheets with Google Drive integration",
378
+ "category": "productivity",
379
+ "tags": ["productivity", "google", "spreadsheets", "sheets", "data"],
380
+ "transport": "stdio",
381
+ "package": "mcp-google-sheets",
382
+ "command": "npx",
383
+ "args": ["-y", "mcp-google-sheets"],
384
+ "envVars": {
385
+ "GOOGLE_SHEETS_CREDENTIALS": {
386
+ "description": "Path to Google service account credentials JSON file",
387
+ "required": true,
388
+ "placeholder": "/path/to/credentials.json"
389
+ }
390
+ },
391
+ "tools": [
392
+ { "name": "create_spreadsheet", "description": "Create a new spreadsheet" },
393
+ { "name": "get_spreadsheet", "description": "Get spreadsheet data" },
394
+ { "name": "update_cells", "description": "Update cell values" },
395
+ { "name": "append_rows", "description": "Append rows to a sheet" },
396
+ { "name": "create_sheet", "description": "Create a new sheet tab" },
397
+ { "name": "list_sheets", "description": "List sheets in a spreadsheet" }
398
+ ],
399
+ "maturity": "community",
400
+ "vendor": "xing5",
401
+ "docsUrl": "https://github.com/xing5/mcp-google-sheets"
402
+ },
403
+ {
404
+ "name": "fetch",
405
+ "displayName": "Web Fetch",
406
+ "description": "Fetch any URL and return its content as markdown — no API keys required",
407
+ "category": "search",
408
+ "tags": ["search", "web", "fetch", "url", "scrape", "markdown"],
409
+ "transport": "stdio",
410
+ "command": "uvx",
411
+ "args": ["mcp-server-fetch"],
412
+ "envVars": {},
413
+ "tools": [
414
+ { "name": "fetch", "description": "Fetch a URL and return content as markdown" }
415
+ ],
416
+ "maturity": "official",
417
+ "vendor": "MCP Project",
418
+ "docsUrl": "https://github.com/modelcontextprotocol/servers/tree/main/src/fetch"
419
+ },
420
+ {
421
+ "name": "crawl4ai",
422
+ "displayName": "Crawl4AI",
423
+ "description": "Local Docker-based web crawler — scrape pages as markdown/HTML, take screenshots, extract PDFs, and execute JS. No API keys required.",
424
+ "category": "search",
425
+ "tags": ["search", "web", "crawl", "scrape", "markdown", "screenshot", "docker"],
426
+ "transport": "sse",
427
+ "url": "http://localhost:11235/mcp/sse",
428
+ "envVars": {},
429
+ "tools": [
430
+ { "name": "md", "description": "Crawl a URL and return content as markdown" },
431
+ { "name": "html", "description": "Crawl a URL and return raw HTML" },
432
+ { "name": "screenshot", "description": "Take a screenshot of a webpage" },
433
+ { "name": "pdf", "description": "Generate a PDF of a webpage" },
434
+ { "name": "execute_js", "description": "Execute JavaScript on a page and return results" },
435
+ { "name": "crawl", "description": "Crawl a URL with full configuration options" },
436
+ { "name": "ask", "description": "Ask a question about a webpage's content" }
437
+ ],
438
+ "maturity": "community",
439
+ "vendor": "unclecode",
440
+ "docsUrl": "https://github.com/unclecode/crawl4ai"
441
+ },
442
+ {
443
+ "type": "prompt",
444
+ "name": "tone-guide",
445
+ "displayName": "Tone Guide",
446
+ "description": "Warm, professional communication style for customer-facing agents",
447
+ "category": "support",
448
+ "tags": ["support", "tone", "communication", "style"],
449
+ "content": "## Communication Style\n\n- Warm, professional, and concise\n- Use short paragraphs and bullet points for clarity\n- Acknowledge the customer's frustration before problem-solving\n- Avoid jargon — use plain language\n- End each interaction with a clear next step or confirmation",
450
+ "maturity": "official",
451
+ "vendor": "Operor"
452
+ },
453
+ {
454
+ "type": "prompt",
455
+ "name": "escalation-protocol",
456
+ "displayName": "Escalation Protocol",
457
+ "description": "Standard escalation behavior for customer support agents",
458
+ "category": "support",
459
+ "tags": ["support", "escalation", "handoff"],
460
+ "content": "## Escalation Protocol\n\n- If the customer asks for a human agent, acknowledge their request and provide handoff instructions\n- If the issue involves a refund over the policy limit, escalate to the billing team\n- If the customer is abusive or threatening, politely end the conversation and escalate\n- Use the phrase: \"Let me connect you with a team member who can help with this.\"",
461
+ "maturity": "official",
462
+ "vendor": "Operor"
463
+ }
464
+ ]
465
+ }
@@ -0,0 +1,154 @@
1
+ import type { Skill, Tool } from '@operor/core';
2
+ import { createMCPClient } from '@ai-sdk/mcp';
3
+ import type { MCPSkillConfig } from './config.js';
4
+ import { resolveEnvVars, resolveEnvRecord } from './config.js';
5
+
6
+ export class MCPSkill implements Skill {
7
+ public readonly name: string;
8
+ public tools: Record<string, Tool> = {};
9
+ private client: any = null;
10
+ private ready = false;
11
+ private config: MCPSkillConfig;
12
+
13
+ constructor(config: MCPSkillConfig) {
14
+ this.config = config;
15
+ this.name = config.name;
16
+ }
17
+
18
+ async initialize(): Promise<void> {
19
+ if (this.ready) return;
20
+ const transport = await this.createTransport();
21
+
22
+ this.client = await createMCPClient({ transport });
23
+
24
+ // Discover tools via the AI SDK MCP client
25
+ const mcpTools = await this.client.tools();
26
+ const prefix = this.config.toolPrefix ?? this.name;
27
+
28
+ for (const [toolName, tool] of Object.entries(mcpTools) as [string, any][]) {
29
+ const prefixedName = `${prefix}__${toolName}`;
30
+
31
+ // Extract the JSON Schema from the AI SDK tool's parameters
32
+ const parameters = this.extractParameters(tool);
33
+
34
+ this.tools[prefixedName] = {
35
+ name: prefixedName,
36
+ description: tool.description || '',
37
+ parameters,
38
+ execute: async (params: any) => {
39
+ // Call the AI SDK tool's execute function directly
40
+ const result = await tool.execute(params, { toolCallId: `call_${Date.now()}` });
41
+ // MCP tools return { content: [...], isError: true } on failure
42
+ // instead of throwing — surface these as real errors
43
+ if (result && typeof result === 'object' && result.isError) {
44
+ const msg = result.content?.[0]?.text
45
+ || (result.content?.length ? JSON.stringify(result.content) : null)
46
+ || 'MCP tool returned an error (no details provided)';
47
+ throw new Error(msg);
48
+ }
49
+ return result;
50
+ },
51
+ };
52
+ }
53
+
54
+ this.ready = true;
55
+ }
56
+
57
+ isReady(): boolean {
58
+ return this.ready;
59
+ }
60
+
61
+ async close(): Promise<void> {
62
+ if (this.client) {
63
+ await this.client.close();
64
+ this.client = null;
65
+ }
66
+ this.ready = false;
67
+ }
68
+
69
+ /**
70
+ * Extract parameters from an AI SDK tool into JSON Schema format.
71
+ * The @ai-sdk/mcp client exposes the schema as `inputSchema` (a jsonSchema()
72
+ * wrapper with a `.jsonSchema` getter), while older/test code may use
73
+ * `parameters` directly.
74
+ */
75
+ private extractParameters(tool: any): Record<string, any> {
76
+ // AI SDK MCP tools use `inputSchema` (a jsonSchema() wrapper)
77
+ const schema = tool.inputSchema ?? tool.parameters;
78
+ if (schema) {
79
+ // jsonSchema() wrapper — has a `.jsonSchema` getter
80
+ if (schema.jsonSchema) {
81
+ return schema.jsonSchema;
82
+ }
83
+ // Plain JSON Schema object
84
+ if (schema.type === 'object' && schema.properties) {
85
+ return schema;
86
+ }
87
+ }
88
+ return { type: 'object', properties: {} };
89
+ }
90
+
91
+ /**
92
+ * Create the appropriate transport based on config.
93
+ */
94
+ private async createTransport(): Promise<any> {
95
+ switch (this.config.transport) {
96
+ case 'stdio':
97
+ return await this.createStdioTransport();
98
+ case 'http':
99
+ return this.createHttpTransport();
100
+ case 'sse':
101
+ return this.createSseTransport();
102
+ default:
103
+ throw new Error(`Unknown transport type: ${this.config.transport}`);
104
+ }
105
+ }
106
+
107
+ private async createStdioTransport(): Promise<any> {
108
+ if (!this.config.command) {
109
+ throw new Error(`Skill "${this.name}": stdio transport requires a "command" field`);
110
+ }
111
+
112
+ // Resolve env vars in args
113
+ const args = this.config.args?.map(resolveEnvVars) ?? [];
114
+
115
+ // Resolve env vars in env record
116
+ const env = this.config.env ? resolveEnvRecord(this.config.env) : undefined;
117
+
118
+ // ESM dynamic import for @modelcontextprotocol/sdk stdio transport
119
+ const { StdioClientTransport } = await import('@modelcontextprotocol/sdk/client/stdio.js');
120
+ return new StdioClientTransport({
121
+ command: this.config.command,
122
+ args,
123
+ env: env ? { ...process.env, ...env } : undefined,
124
+ });
125
+ }
126
+
127
+ private createHttpTransport(): any {
128
+ if (!this.config.url) {
129
+ throw new Error(`Skill "${this.name}": http transport requires a "url" field`);
130
+ }
131
+
132
+ const headers = this.config.headers ? resolveEnvRecord(this.config.headers) : undefined;
133
+
134
+ return {
135
+ type: 'http' as const,
136
+ url: resolveEnvVars(this.config.url),
137
+ headers,
138
+ };
139
+ }
140
+
141
+ private createSseTransport(): any {
142
+ if (!this.config.url) {
143
+ throw new Error(`Skill "${this.name}": sse transport requires a "url" field`);
144
+ }
145
+
146
+ const headers = this.config.headers ? resolveEnvRecord(this.config.headers) : undefined;
147
+
148
+ return {
149
+ type: 'sse' as const,
150
+ url: resolveEnvVars(this.config.url),
151
+ headers,
152
+ };
153
+ }
154
+ }
@@ -0,0 +1,38 @@
1
+ import type { Skill, Tool } from '@operor/core';
2
+ import type { PromptSkillConfig } from './config.js';
3
+
4
+ export class PromptSkill implements Skill {
5
+ public readonly name: string;
6
+ public readonly tools: Record<string, Tool> = {};
7
+ private ready = false;
8
+ private _config: PromptSkillConfig;
9
+
10
+ constructor(config: PromptSkillConfig) {
11
+ this._config = config;
12
+ this.name = config.name;
13
+ }
14
+
15
+ async initialize(): Promise<void> {
16
+ if (this.ready) return;
17
+ if (!this._config.content?.trim()) {
18
+ throw new Error(`Prompt skill "${this.name}": content must be a non-empty string`);
19
+ }
20
+ this.ready = true;
21
+ }
22
+
23
+ isReady(): boolean {
24
+ return this.ready;
25
+ }
26
+
27
+ async close(): Promise<void> {
28
+ this.ready = false;
29
+ }
30
+
31
+ getContent(): string {
32
+ return this._config.content;
33
+ }
34
+
35
+ getConfig(): PromptSkillConfig {
36
+ return { ...this._config };
37
+ }
38
+ }