E585 Updates for mcp debugging · docker/labs-ai-tools-vscode@ead8023 · GitHub
[go: up one dir, main page]

Skip to content

Commit ead8023

Browse files
author
colinmcneil
committed
Updates for mcp debugging
1 parent 9b38e38 commit ead8023

File tree

7 files changed

+226
-38
lines changed

7 files changed

+226
-38
lines changed

package.json

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "labs-ai-tools-vscode",
33
"displayName": "Labs: AI Tools for VSCode",
44
"description": "Run & Debug AI Prompts with Dockerized tools",
5-
"version": "0.1.10",
5+
"version": "0.1.11",
66
"publisher": "docker",
77
"repository": {
88
"type": "git",
@@ -61,12 +61,13 @@
6161
"title": "Docker AI: Set Secret Key"
6262
},
6363
{
64-
"command": "docker.labs-ai-tools-vscode.save-prompt",
65-
"title": "Docker AI: Save a prompt"
64+
"command": "docker.labs-ai-tools-vscode.registry",
65+
"title": "Docker AI: Manage prompt registry"
6666
},
6767
{
68-
"command": "docker.labs-ai-tools-vscode.delete-prompt",
69-
"title": "Docker AI: Delete a saved prompt"
68+
"command": "docker.labs-ai-tools-vscode.register",
69+
"title": "Docker AI: Register this prompt",
70+
"when": "editorLangId == markdown"
7071
},
7172
{
7273
"command": "docker.labs-ai-tools-vscode.run-file-as-prompt",

src/commands/index.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,15 @@
22
import * as vscode from 'vscode'
33
import { runPrompt } from './runPrompt';
44
import { runHotCommand } from './runHotCommand';
5-
import { deletePrompt, savePrompt } from './manageSavedPrompts';
65
import { setProjectDir } from './setProjectDir';
76
import { setThreadId } from './setThreadId';
87
import killActivePrompts from './killActivePrompts';
8+
import { registerOpenPrompt, registerPrompt, showRegistryInput } from '../utils/promptRegistery';
99

10-
type CTX = { secrets: any }
10+
type CTX = vscode.ExtensionContext
1111

1212
const commands = (context: CTX) => [
1313
{ id: 'docker.labs-ai-tools-vscode.run-commands', callback: runHotCommand },
14-
{ id: 'docker.labs-ai-tools-vscode.save-prompt', callback: savePrompt },
15-
{ id: 'docker.labs-ai-tools-vscode.delete-prompt', callback: deletePrompt },
1614
{ id: 'docker.labs-ai-tools-vscode.run-workspace-as-prompt', callback: () => runPrompt(context.secrets, 'local-dir') },
1715
{ id: 'docker.labs-ai-tools-vscode.run-file-as-prompt', callback: () => runPrompt(context.secrets, 'local-file') },
1816
{ id: 'docker.labs-ai-tools-vscode.run-prompt', callback: () => runPrompt(context.secrets, 'remote') },
@@ -27,6 +25,9 @@ const commands = (context: CTX) => [
2725
}
2826
},
2927
{ id: 'docker.labs-ai-tools-vscode.kill-active-prompts', callback: killActivePrompts },
28+
{ id: 'docker.labs-ai-tools-vscode.register-prompt', callback: registerPrompt },
29+
{ id: 'docker.labs-ai-tools-vscode.registry', callback: () => showRegistryInput(context) },
30+
{ id: 'docker.labs-ai-tools-vscode.register', callback: () => registerOpenPrompt(context) },
3031
]
3132

3233
export default (context: CTX) => commands(context).map((comm) => vscode.commands.registerCommand(comm.id, comm.callback))

src/commands/manageSavedPrompts.ts

Lines changed: 0 additions & 22 deletions
This file was deleted.

src/extension.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -89,17 +89,22 @@ export async function activate(context: vscode.ExtensionContext) {
8989
});
9090
context.subscriptions.push(setProviderSecretCommand);
9191

92-
const pullPromptImage = () => {
93-
const process = spawn('docker', ['pull', "vonwig/prompts:latest"]);
92+
const pullPromptImagePromise = new Promise((resolve, reject) => {
93+
const process = spawn('docker', ['pull', "mcp/docker:latest"]);
9494
process.stdout.on('data', (data) => {
9595
console.error(data.toString());
9696
});
9797
process.stderr.on('data', (data) => {
9898
console.error(data.toString());
9999
});
100-
}
101-
102-
pullPromptImage();
100+
process.on('close', (code) => {
101+
if (code === 0) {
102+
resolve(true);
103+
} else {
104+
reject(new Error('Failed to pull prompt image'));
105+
}
106+
});
107+
});
103108

104109
const registeredCommands = commands(context)
105110

@@ -149,4 +154,5 @@ export async function activate(context: vscode.ExtensionContext) {
149154
}
150155
)
151156
);
157+
await pullPromptImagePromise;
152158
}

src/promptmetadatagrammar.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
],
99
"repository": {
1010
"prompt-metadata-keyword": {
11-
"match": "(?i)(functions)|(extractors)|(model)|(stream)|(url)|(tools)(?-i)",
11+
"match": "(?i)((functions)|(extractors)|(model)|(stream)|(url)|(tools)|(description)|(parameters)):(?-i)",
1212
"name": "keyword.function.prompt.metadata"
1313
}
1414
}

src/utils/promptRegistery.ts

Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
import path from 'path';
2+
import * as vscode from 'vscode';
3+
4+
interface RegisteredPrompt extends vscode.QuickPickItem {
5+
tag: string;
6+
ref: string; // Either a local path or a git ref: `<provider>:<owner>/<repo>?path=<path>`
7+
mcp: boolean;
8+
}
9+
10+
export const showRegistryInput = async (context: vscode.ExtensionContext) => {
11+
let registry = context.globalState.get('registry') as { [key: string]: RegisteredPrompt } || {};
12+
if (Object.keys(registry).length === 0) {
13+
registry = {
14+
'placeholder': {
15+
label: 'Placeholder',
16+
ref: 'github:docker/labs-ai-tools-for-devs?path=prompts/npm-project.md',
17+
mcp: false,
18+
tag: 'placeholder'
19+
},
20+
'placeholder2': {
21+
label: 'Placeholder2',
22+
ref: 'github:docker/labs-ai-tools-for-devs?path=prompts/examples/explain_dockerfile.md',
23+
mcp: true,
24+
tag: 'placeholder2'
25+
},
26+
'placeholder3': {
27+
label: 'Placeholder3',
28+
ref: 'github:docker/labs-ai-tools-for-devs?path=prompts/examples/curl.md',
29+
mcp: false,
30+
tag: 'placeholder3'
31+
}
32+
,
33+
'placeholder4': {
34+
label: 'Placeholder4',
35+
ref: '~/Dev/labs-ai-tools-for-devs/prompts/examples/curl.md',
36+
mcp: false,
37+
tag: 'placeholder4'
38+
}
39+
};
40+
await context.globalState.update('registry', registry);
41+
}
42+
const refreshItems = async () => {
43+
input.items = Object.values(await context.globalState.get('registry') as { [key: string]: RegisteredPrompt }).map(registeredPrompt => ({
44+
...registeredPrompt, buttons:
45+
[
46+
{
47+
tooltip: 'Run',
48+
iconPath: new vscode.ThemeIcon('run')
49+
},
50+
{
51+
tooltip: 'Open',
52+
text: 'Open',
53+
iconPath: new vscode.ThemeIcon('open-preview')
54+
}
55+
],
56+
detail: registeredPrompt.mcp ? 'MCP' : 'Not MCP'
57+
}));
58+
}
59+
const input = vscode.window.createQuickPick<RegisteredPrompt>();
60+
input.canSelectMany = true;
61+
input.matchOnDescription = true;
62+
input.matchOnDetail = true;
63+
input.placeholder = 'Start typing to search or paste a prompt reference to register';
64+
refreshItems();
65+
input.onDidChangeSelection((selection) => {
66+
if (selection.length > 0 && input.buttons.find(button => button.tooltip === 'Delete all') === undefined) {
67+
input.buttons = [
68+
{
69+
tooltip: 'Delete all',
70+
iconPath: new vscode.ThemeIcon('trash')
71+
},
72+
{
73+
tooltip: 'Register selection as MCP servers',
74+
iconPath: new vscode.ThemeIcon('eye')
75+
},
76+
{
77+
tooltip: 'Unregister selection as MCP servers',
78+
iconPath: new vscode.ThemeIcon('eye-closed')
79+
}
80+
];
81+
}
82+
if (selection.length === 0 && !input.buttons.find(button => button.tooltip === 'Delete all')) {
83+
input.buttons = [];
84+
}
85+
});
86+
input.onDidChangeValue((value) => {
87+
try {
88+
const uri = convertRefInputToURI(value);
89+
input.items = [...input.items, { label: `Register ${uri.path}`, ref: uri.toString(), mcp: false, tag: uri.path, detail: uri.toString() }];
90+
} catch (e) {
91+
// Input is not a valid reference
92+
}
93+
});
94+
input.onDidAccept(() => {
95+
input.hide();
96+
if (input.selectedItems.length === 0) {
97+
98+
}
99+
100+
});
101+
input.onDidTriggerButton(async (button) => {
102+
if (button.tooltip === 'Delete all') {
103+
const reg = context.globalState.get('registry') as { [key: string]: RegisteredPrompt };
104+
let deleted = 0;
105+
for (const item of input.selectedItems) {
106+
delete reg[item.tag];
107+
deleted++;
108+
}
109+
vscode.window.showInformationMessage(`Deleted ${deleted} prompts`);
110+
await context.globalState.update('registry', reg);
111+
await refreshItems();
112+
}
113+
if (button.tooltip === 'Register selection as MCP servers') {
114+
const reg = context.globalState.get('registry') as { [key: string]: RegisteredPrompt };
115+
let registered = 0;
116+
for (const item of input.selectedItems) {
117+
reg[item.tag].mcp = true;
118+
registered++;
119+
}
120+
vscode.window.showInformationMessage(`Registered ${registered} prompts as MCP servers`);
121+
await context.globalState.update('registry', reg);
122+
await refreshItems();
123+
}
124+
if (button.tooltip === 'Unregister selection as MCP servers') {
125+
const reg = context.globalState.get('registry') as { [key: string]: RegisteredPrompt };
126+
let unregistered = 0;
127+
for (const item of input.selectedItems) {
128+
reg[item.tag].mcp = false;
129+
unregistered++;
130+
}
131+
vscode.window.showInformationMessage(`Unregistered ${unregistered} prompts as MCP servers`);
132+
await context.globalState.update('registry', reg);
133+
await refreshItems();
134+
}
135+
});
136+
input.show();
137+
}
138+
139+
export const convertRefInputToURI = (ref: string): vscode.Uri => {
140+
// Ref is a git ref
141+
if (ref.match(/^.*:.*\/$/)) {
142+
let [provider, rest] = ref.split(':');
143+
let [owner, rest2] = rest.split('/');
144+
let [repo, path] = rest2.split('?path=');
145+
return vscode.Uri.parse(`https://${provider}.com/${owner}/${repo}/blob/main/${path}`);
146+
}
147+
// Local path
148+
try {
149+
return vscode.Uri.file 50E0 (ref);
150+
} catch (e) {
151+
throw new Error(`Invalid reference: ${ref}`);
152+
}
153+
}
154+
155+
export const registerPrompt = (context: vscode.ExtensionContext, ref: string, tag: string) => {
156+
const registry = context.globalState.get('registry') as { [key: string]: RegisteredPrompt };
157+
registry[tag] = { label: tag, ref: ref.toString(), mcp: false, tag };
158+
context.globalState.update('registry', registry);
159+
}
160+
161+
export const registerOpenPrompt = async (context: vscode.ExtensionContext) => {
162+
// If no open markdown editor, return
163+
const editor = vscode.window.activeTextEditor;
164+
if (!editor || editor.document.languageId !== 'markdown') {
165+
return;
166+
}
167+
// If open markdown editor, get the tag from the file name
168+
const markdownPath = editor.document.uri.fsPath;
169+
const tag = path.basename(markdownPath);
170+
const registry = await context.globalState.get('registry') as { [key: string]: RegisteredPrompt };
171+
if (registry[tag]) {
172+
if (registry[tag].ref === markdownPath) {
173+
const option = await vscode.window.showErrorMessage(`You have already registered this prompt.`, 'Open', 'Unregister');
174+
if (option === 'Open') {
175+
return vscode.commands.executeCommand('vscode.open', vscode.Uri.parse(registry[tag].ref));
176+
}
177+
if (option === 'Unregister') {
178+
delete registry[tag];
179+
await context.globalState.update('registry', registry);
180+
return vscode.window.showInformationMessage(`Unregistered prompt ${tag}`);
181+
}
182+
}
183+
else {
184+
return vscode.window.showErrorMessage(`Prompt ${tag} already registered at ${registry[tag].ref}`);
185+
}
186+
187+
}
188+
registerPrompt(context, markdownPath, tag);
189+
return vscode.window.showInformationMessage(`Registered prompt ${tag}`);
190+
}
191+
192+
/**
193+
* TODO Q's:
194+
* Default MCP servers?
195+
* Update to new version migration?
196+
* Two commands or one command? Registry vs MCP
197+
* Local ref == path?
198+
* Ref conversions?
199+
* Language + Copy --> MCP debugging
200+
* Connecting to anthropic config?
201+
*
202+
*/

src/utils/promptRunner.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ export const getRunArgs = async (promptRef: string, projectDir: string, username
4444
];
4545

4646
const runArgs: string[] = render ? [] : [
47-
'vonwig/prompts:latest',
47+
'mcp/docker:latest',
4848
...(render ? [] : ['run']),
4949
"--host-dir", projectDir,
5050
"--user", username,

0 commit comments

Comments
 (0)
0