-
Notifications
You must be signed in to change notification settings - Fork 14
feat(tools): add surveys tools #117
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
"category": "Surveys", | ||
"summary": "Update an existing survey by ID." | ||
}, | ||
"survey-delete": { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we call it 'delete' on the UI?
Or do we have both states? Arquive and really deleted?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the UI we have "Delete" for all surveys (permanent) and "Archive" for stopped or completed surveys. Afaik, we don't have an actual soft delete.
Had the same concerns as this comment here, so I went with always archive from mcp instead
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is looking great! Will give it a test run now locally - left some comments, main question is whether the LLM handles the input complexity well enough
typescript/src/api/client.ts
Outdated
const validatedInput = CreateSurveyInputSchema.parse(data); | ||
|
||
const createResponseSchema = z.object({ | ||
id: z.string(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably worth including more information about the created survey in the response here and in the other tools?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done, returning the whole survey object which helps with "next steps" as well
delete: async ({ | ||
surveyId, | ||
softDelete = true, | ||
}: { surveyId: string; softDelete?: boolean }): Promise< |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we can just make this a soft delete only to avoid things going too wrong
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added this optional param just for integration test cleanup (so we are left with a clean project).
The deleteHandler
actually omits this param. My understanding is that the agent will always soft delete this way, right?
typescript/src/schema/surveys.ts
Outdated
// Output schema - permissive for API responses | ||
export const SurveyQuestionOutputSchema = z.object({ | ||
type: z.string(), | ||
question: z.string().optional(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These should all use .nullish() to allow null and undefined values
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done. Thought optional() was doing it but reading up it doesn't actually include null values
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These look great and really detailed with some useful error messages - when you were testing this locally, was the LLM able to handle the complexity of the input? We might need to slightly restrict the kinds of surveys that can be made if not.
typescript/src/schema/tool-inputs.ts
Outdated
}); | ||
|
||
export const SurveyGetAllSchema = z.object({ | ||
data: ListSurveysInputSchema.optional(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can probably be just ListSurveysInputSchema
directly
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
annotations: { | ||
destructiveHint: true, | ||
idempotentHint: false, | ||
openWorldHint: false, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
openWorldHint: false, | |
openWorldHint: true, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we should put this as true for this and the other tools
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
} | ||
|
||
// Format the surveys with better status display | ||
const formattedSurveys = surveysResult.data.map((survey) => ({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we pull this out into a function and then apply it elsewhere as well, so that we are being consistent with the output across tools?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense to include the url here too
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done, pulled into a utils function
const { surveyId, ...data } = params; | ||
const projectId = await context.stateManager.getProjectId(); | ||
|
||
if (data.questions) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we've got this logic here, it probably should also be in the create tool
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done. Single choice question branching is index based, but agent kept using the option text as the key. So my solution was to use Int in input schema, then map to string before making the call. I'm sure there's a better way though
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is looking great! Will give it a test run now locally - left some comments, main question is whether the LLM handles the input complexity well enough
Did some testing locally, a few thoughts:
Let me know if you want any more testing :) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey @joshsny, thanks for reviewing this. I tried to address all the comments here.
Regarding the following
Did some testing locally, a few thoughts:
- The stats tools were not returning results for me, just the survey name, id and some other details, not sure if I was doing this wrong?
This is based on responses, so for a fresh survey with no responses I guess it's because the response counts were zero? Tried running it for a survey with some responses and got some stats back
- Surveys list works great
- I got 'Permission Denied' whilst trying to use the create survey tool, I think it might just be struggling to get the input right as it's quite complex, I think we might need to simplify inputs here
- Same for update, we probably need to simplify the input schema the same as the create one
When I was testing, Permission Denied was usually trying to create or update a survey to a project that was not in my Personal API key. I'm attaching some screenshots below with some prompts I tried. Looks like it's handing it okay but tbh I have no idea how to test this thoroughly.

This prompt here created the survey correctly with
- Reasonable title and theme
- Conditional logic on detractors with a reasonable follow up question
- Correct display conditions


Then the follow-up prompt to link this to a feature flag seemed to work okay as well


Not sure if my prompts are just a bit too specific and giving it too much guidance though.
typescript/src/api/client.ts
Outdated
const validatedInput = CreateSurveyInputSchema.parse(data); | ||
|
||
const createResponseSchema = z.object({ | ||
id: z.string(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done, returning the whole survey object which helps with "next steps" as well
delete: async ({ | ||
surveyId, | ||
softDelete = true, | ||
}: { surveyId: string; softDelete?: boolean }): Promise< |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added this optional param just for integration test cleanup (so we are left with a clean project).
The deleteHandler
actually omits this param. My understanding is that the agent will always soft delete this way, right?
"category": "Surveys", | ||
"summary": "Update an existing survey by ID." | ||
}, | ||
"survey-delete": { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the UI we have "Delete" for all surveys (permanent) and "Archive" for stopped or completed surveys. Afaik, we don't have an actual soft delete.
Had the same concerns as this comment here, so I went with always archive from mcp instead
typescript/src/schema/tool-inputs.ts
Outdated
}); | ||
|
||
export const SurveyGetAllSchema = z.object({ | ||
data: ListSurveysInputSchema.optional(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
annotations: { | ||
destructiveHint: true, | ||
idempotentHint: false, | ||
openWorldHint: false, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
} | ||
|
||
// Format the surveys with better status display | ||
const formattedSurveys = surveysResult.data.map((survey) => ({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done, pulled into a utils function
const { surveyId, ...data } = params; | ||
const projectId = await context.stateManager.getProjectId(); | ||
|
||
if (data.questions) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done. Single choice question branching is index based, but agent kept using the option text as the key. So my solution was to use Int in input schema, then map to string before making the call. I'm sure there's a better way though
typescript/src/schema/surveys.ts
Outdated
// Output schema - permissive for API responses | ||
export const SurveyQuestionOutputSchema = z.object({ | ||
type: z.string(), | ||
question: z.string().optional(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done. Thought optional() was doing it but reading up it doesn't actually include null values
Awesome - thanks again for this @ioannisj - looks great! Since you are on paternity from Monday, I can merge this in next week - have a great time! |
Description
Towards #87
Adds a set of survey-related tools:
survey-create
: Creates a new survey in the project.survey-get
: Get a specific survey by ID.surveys-get-all
: Get all surveys in the project with optional filtering.survey-update
: Update an existing survey by ID.survey-delete
: Delete a survey by ID (soft delete - marks as archived.Plus:
surveys-global-stats
: Get aggregated response statistics across all surveys in the project.survey-stats
: Get response statistics for a specific survey.This is just CRUD operations mostly. Much more valuable response analysis tools will soon come from @marandaneto
Tried to follow tools documentation. Fair warning: this is my first time touching these tools and language, so excuse any naive bits of code
Tests?